1 /* $NetBSD: authfd.c,v 1.21 2020/05/28 17:05:49 christos Exp $ */ 2 /* $OpenBSD: authfd.c,v 1.123 2020/03/06 18:24:39 markus 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.21 2020/05/28 17:05:49 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 <stdarg.h> 50 #include <unistd.h> 51 #include <errno.h> 52 53 #include "xmalloc.h" 54 #include "ssh.h" 55 #include "sshbuf.h" 56 #include "sshkey.h" 57 #include "authfd.h" 58 #include "cipher.h" 59 #include "compat.h" 60 #include "log.h" 61 #include "atomicio.h" 62 #include "misc.h" 63 #include "ssherr.h" 64 65 #define MAX_AGENT_IDENTITIES 2048 /* Max keys in agent reply */ 66 #define MAX_AGENT_REPLY_LEN (256 * 1024) /* Max bytes in agent reply */ 67 68 /* macro to check for "agent failure" message */ 69 #define agent_failed(x) \ 70 ((x == SSH_AGENT_FAILURE) || \ 71 (x == SSH_COM_AGENT2_FAILURE) || \ 72 (x == SSH2_AGENT_FAILURE)) 73 74 /* Convert success/failure response from agent to a err.h status */ 75 static int 76 decode_reply(u_char type) 77 { 78 if (agent_failed(type)) 79 return SSH_ERR_AGENT_FAILURE; 80 else if (type == SSH_AGENT_SUCCESS) 81 return 0; 82 else 83 return SSH_ERR_INVALID_FORMAT; 84 } 85 86 /* 87 * Opens an authentication socket at the provided path and stores the file 88 * descriptor in fdp. Returns 0 on success and an error on failure. 89 */ 90 int 91 ssh_get_authentication_socket_path(const char *authsocket, int *fdp) 92 { 93 int sock, oerrno; 94 struct sockaddr_un sunaddr; 95 96 memset(&sunaddr, 0, sizeof(sunaddr)); 97 sunaddr.sun_family = AF_UNIX; 98 strlcpy(sunaddr.sun_path, authsocket, sizeof(sunaddr.sun_path)); 99 100 if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 101 return SSH_ERR_SYSTEM_ERROR; 102 103 /* close on exec */ 104 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1 || 105 connect(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) { 106 oerrno = errno; 107 close(sock); 108 errno = oerrno; 109 return SSH_ERR_SYSTEM_ERROR; 110 } 111 if (fdp != NULL) 112 *fdp = sock; 113 else 114 close(sock); 115 return 0; 116 } 117 118 /* 119 * Opens the default authentication socket and stores the file descriptor in 120 * fdp. Returns 0 on success and an error on failure. 121 */ 122 int 123 ssh_get_authentication_socket(int *fdp) 124 { 125 const char *authsocket; 126 127 if (fdp != NULL) 128 *fdp = -1; 129 130 authsocket = getenv(SSH_AUTHSOCKET_ENV_NAME); 131 if (authsocket == NULL || *authsocket == '\0') 132 return SSH_ERR_AGENT_NOT_PRESENT; 133 134 return ssh_get_authentication_socket_path(authsocket, fdp); 135 } 136 137 /* Communicate with agent: send request and read reply */ 138 static int 139 ssh_request_reply(int sock, struct sshbuf *request, struct sshbuf *reply) 140 { 141 int r; 142 size_t l, len; 143 char buf[1024]; 144 145 /* Get the length of the message, and format it in the buffer. */ 146 len = sshbuf_len(request); 147 POKE_U32(buf, len); 148 149 /* Send the length and then the packet to the agent. */ 150 if (atomicio(vwrite, sock, buf, 4) != 4 || 151 atomicio(vwrite, sock, sshbuf_mutable_ptr(request), 152 sshbuf_len(request)) != sshbuf_len(request)) 153 return SSH_ERR_AGENT_COMMUNICATION; 154 /* 155 * Wait for response from the agent. First read the length of the 156 * response packet. 157 */ 158 if (atomicio(read, sock, buf, 4) != 4) 159 return SSH_ERR_AGENT_COMMUNICATION; 160 161 /* Extract the length, and check it for sanity. */ 162 len = PEEK_U32(buf); 163 if (len > MAX_AGENT_REPLY_LEN) 164 return SSH_ERR_INVALID_FORMAT; 165 166 /* Read the rest of the response in to the buffer. */ 167 sshbuf_reset(reply); 168 while (len > 0) { 169 l = len; 170 if (l > sizeof(buf)) 171 l = sizeof(buf); 172 if (atomicio(read, sock, buf, l) != l) 173 return SSH_ERR_AGENT_COMMUNICATION; 174 if ((r = sshbuf_put(reply, buf, l)) != 0) 175 return r; 176 len -= l; 177 } 178 return 0; 179 } 180 181 /* 182 * Closes the agent socket if it should be closed (depends on how it was 183 * obtained). The argument must have been returned by 184 * ssh_get_authentication_socket(). 185 */ 186 void 187 ssh_close_authentication_socket(int sock) 188 { 189 if (getenv(SSH_AUTHSOCKET_ENV_NAME)) 190 close(sock); 191 } 192 193 /* Lock/unlock agent */ 194 int 195 ssh_lock_agent(int sock, int lock, const char *password) 196 { 197 int r; 198 u_char type = lock ? SSH_AGENTC_LOCK : SSH_AGENTC_UNLOCK; 199 struct sshbuf *msg; 200 201 if ((msg = sshbuf_new()) == NULL) 202 return SSH_ERR_ALLOC_FAIL; 203 if ((r = sshbuf_put_u8(msg, type)) != 0 || 204 (r = sshbuf_put_cstring(msg, password)) != 0) 205 goto out; 206 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 207 goto out; 208 if ((r = sshbuf_get_u8(msg, &type)) != 0) 209 goto out; 210 r = decode_reply(type); 211 out: 212 sshbuf_free(msg); 213 return r; 214 } 215 216 217 static int 218 deserialise_identity2(struct sshbuf *ids, struct sshkey **keyp, char **commentp) 219 { 220 int r; 221 char *comment = NULL; 222 const u_char *blob; 223 size_t blen; 224 225 if ((r = sshbuf_get_string_direct(ids, &blob, &blen)) != 0 || 226 (r = sshbuf_get_cstring(ids, &comment, NULL)) != 0) 227 goto out; 228 if ((r = sshkey_from_blob(blob, blen, keyp)) != 0) 229 goto out; 230 if (commentp != NULL) { 231 *commentp = comment; 232 comment = NULL; 233 } 234 r = 0; 235 out: 236 free(comment); 237 return r; 238 } 239 240 /* 241 * Fetch list of identities held by the agent. 242 */ 243 int 244 ssh_fetch_identitylist(int sock, struct ssh_identitylist **idlp) 245 { 246 u_char type; 247 u_int32_t num, i; 248 struct sshbuf *msg; 249 struct ssh_identitylist *idl = NULL; 250 int r; 251 252 /* 253 * Send a message to the agent requesting for a list of the 254 * identities it can represent. 255 */ 256 if ((msg = sshbuf_new()) == NULL) 257 return SSH_ERR_ALLOC_FAIL; 258 if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_REQUEST_IDENTITIES)) != 0) 259 goto out; 260 261 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 262 goto out; 263 264 /* Get message type, and verify that we got a proper answer. */ 265 if ((r = sshbuf_get_u8(msg, &type)) != 0) 266 goto out; 267 if (agent_failed(type)) { 268 r = SSH_ERR_AGENT_FAILURE; 269 goto out; 270 } else if (type != SSH2_AGENT_IDENTITIES_ANSWER) { 271 r = SSH_ERR_INVALID_FORMAT; 272 goto out; 273 } 274 275 /* Get the number of entries in the response and check it for sanity. */ 276 if ((r = sshbuf_get_u32(msg, &num)) != 0) 277 goto out; 278 if (num > MAX_AGENT_IDENTITIES) { 279 r = SSH_ERR_INVALID_FORMAT; 280 goto out; 281 } 282 if (num == 0) { 283 r = SSH_ERR_AGENT_NO_IDENTITIES; 284 goto out; 285 } 286 287 /* Deserialise the response into a list of keys/comments */ 288 if ((idl = calloc(1, sizeof(*idl))) == NULL || 289 (idl->keys = calloc(num, sizeof(*idl->keys))) == NULL || 290 (idl->comments = calloc(num, sizeof(*idl->comments))) == NULL) { 291 r = SSH_ERR_ALLOC_FAIL; 292 goto out; 293 } 294 for (i = 0; i < num;) { 295 if ((r = deserialise_identity2(msg, &(idl->keys[i]), 296 &(idl->comments[i]))) != 0) { 297 if (r == SSH_ERR_KEY_TYPE_UNKNOWN) { 298 /* Gracefully skip unknown key types */ 299 num--; 300 continue; 301 } else 302 goto out; 303 } 304 i++; 305 } 306 idl->nkeys = num; 307 *idlp = idl; 308 idl = NULL; 309 r = 0; 310 out: 311 sshbuf_free(msg); 312 if (idl != NULL) 313 ssh_free_identitylist(idl); 314 return r; 315 } 316 317 void 318 ssh_free_identitylist(struct ssh_identitylist *idl) 319 { 320 size_t i; 321 322 if (idl == NULL) 323 return; 324 for (i = 0; i < idl->nkeys; i++) { 325 if (idl->keys != NULL) 326 sshkey_free(idl->keys[i]); 327 if (idl->comments != NULL) 328 free(idl->comments[i]); 329 } 330 free(idl->keys); 331 free(idl->comments); 332 free(idl); 333 } 334 335 /* 336 * Check if the ssh agent has a given key. 337 * Returns 0 if found, or a negative SSH_ERR_* error code on failure. 338 */ 339 int 340 ssh_agent_has_key(int sock, struct sshkey *key) 341 { 342 int r, ret = SSH_ERR_KEY_NOT_FOUND; 343 size_t i; 344 struct ssh_identitylist *idlist = NULL; 345 346 if ((r = ssh_fetch_identitylist(sock, &idlist)) != 0) { 347 return r; 348 } 349 350 for (i = 0; i < idlist->nkeys; i++) { 351 if (sshkey_equal_public(idlist->keys[i], key)) { 352 ret = 0; 353 break; 354 } 355 } 356 357 ssh_free_identitylist(idlist); 358 return ret; 359 } 360 361 /* 362 * Sends a challenge (typically from a server via ssh(1)) to the agent, 363 * and waits for a response from the agent. 364 * Returns true (non-zero) if the agent gave the correct answer, zero 365 * otherwise. 366 */ 367 368 369 /* encode signature algorithm in flag bits, so we can keep the msg format */ 370 static u_int 371 agent_encode_alg(const struct sshkey *key, const char *alg) 372 { 373 if (alg != NULL && sshkey_type_plain(key->type) == KEY_RSA) { 374 if (strcmp(alg, "rsa-sha2-256") == 0 || 375 strcmp(alg, "rsa-sha2-256-cert-v01@openssh.com") == 0) 376 return SSH_AGENT_RSA_SHA2_256; 377 if (strcmp(alg, "rsa-sha2-512") == 0 || 378 strcmp(alg, "rsa-sha2-512-cert-v01@openssh.com") == 0) 379 return SSH_AGENT_RSA_SHA2_512; 380 } 381 return 0; 382 } 383 384 /* ask agent to sign data, returns err.h code on error, 0 on success */ 385 int 386 ssh_agent_sign(int sock, const struct sshkey *key, 387 u_char **sigp, size_t *lenp, 388 const u_char *data, size_t datalen, const char *alg, u_int compat) 389 { 390 struct sshbuf *msg; 391 u_char *sig = NULL, type = 0; 392 size_t len = 0; 393 u_int flags = 0; 394 int r = SSH_ERR_INTERNAL_ERROR; 395 396 *sigp = NULL; 397 *lenp = 0; 398 399 if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE) 400 return SSH_ERR_INVALID_ARGUMENT; 401 if ((msg = sshbuf_new()) == NULL) 402 return SSH_ERR_ALLOC_FAIL; 403 flags |= agent_encode_alg(key, alg); 404 if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_SIGN_REQUEST)) != 0 || 405 (r = sshkey_puts(key, msg)) != 0 || 406 (r = sshbuf_put_string(msg, data, datalen)) != 0 || 407 (r = sshbuf_put_u32(msg, flags)) != 0) 408 goto out; 409 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 410 goto out; 411 if ((r = sshbuf_get_u8(msg, &type)) != 0) 412 goto out; 413 if (agent_failed(type)) { 414 r = SSH_ERR_AGENT_FAILURE; 415 goto out; 416 } else if (type != SSH2_AGENT_SIGN_RESPONSE) { 417 r = SSH_ERR_INVALID_FORMAT; 418 goto out; 419 } 420 if ((r = sshbuf_get_string(msg, &sig, &len)) != 0) 421 goto out; 422 /* Check what we actually got back from the agent. */ 423 if ((r = sshkey_check_sigtype(sig, len, alg)) != 0) 424 goto out; 425 /* success */ 426 *sigp = sig; 427 *lenp = len; 428 sig = NULL; 429 len = 0; 430 r = 0; 431 out: 432 freezero(sig, len); 433 sshbuf_free(msg); 434 return r; 435 } 436 437 /* Encode key for a message to the agent. */ 438 439 440 static int 441 encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign, 442 const char *provider) 443 { 444 int r; 445 446 if (life != 0) { 447 if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_LIFETIME)) != 0 || 448 (r = sshbuf_put_u32(m, life)) != 0) 449 goto out; 450 } 451 if (confirm != 0) { 452 if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_CONFIRM)) != 0) 453 goto out; 454 } 455 if (maxsign != 0) { 456 if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_MAXSIGN)) != 0 || 457 (r = sshbuf_put_u32(m, maxsign)) != 0) 458 goto out; 459 } 460 if (provider != NULL) { 461 if ((r = sshbuf_put_u8(m, 462 SSH_AGENT_CONSTRAIN_EXTENSION)) != 0 || 463 (r = sshbuf_put_cstring(m, 464 "sk-provider@openssh.com")) != 0 || 465 (r = sshbuf_put_cstring(m, provider)) != 0) 466 goto out; 467 } 468 r = 0; 469 out: 470 return r; 471 } 472 473 /* 474 * Adds an identity to the authentication server. 475 * This call is intended only for use by ssh-add(1) and like applications. 476 */ 477 int 478 ssh_add_identity_constrained(int sock, struct sshkey *key, 479 const char *comment, u_int life, u_int confirm, u_int maxsign, 480 const char *provider) 481 { 482 struct sshbuf *msg; 483 int r, constrained = (life || confirm || maxsign || provider); 484 u_char type; 485 486 if ((msg = sshbuf_new()) == NULL) 487 return SSH_ERR_ALLOC_FAIL; 488 489 switch (key->type) { 490 #ifdef WITH_OPENSSL 491 case KEY_RSA: 492 case KEY_RSA_CERT: 493 case KEY_DSA: 494 case KEY_DSA_CERT: 495 case KEY_ECDSA: 496 case KEY_ECDSA_CERT: 497 case KEY_ECDSA_SK: 498 case KEY_ECDSA_SK_CERT: 499 #endif 500 case KEY_ED25519: 501 case KEY_ED25519_CERT: 502 case KEY_ED25519_SK: 503 case KEY_ED25519_SK_CERT: 504 case KEY_XMSS: 505 case KEY_XMSS_CERT: 506 type = constrained ? 507 SSH2_AGENTC_ADD_ID_CONSTRAINED : 508 SSH2_AGENTC_ADD_IDENTITY; 509 if ((r = sshbuf_put_u8(msg, type)) != 0 || 510 (r = sshkey_private_serialize_maxsign(key, msg, maxsign, 511 NULL)) != 0 || 512 (r = sshbuf_put_cstring(msg, comment)) != 0) 513 goto out; 514 break; 515 default: 516 r = SSH_ERR_INVALID_ARGUMENT; 517 goto out; 518 } 519 if (constrained && 520 (r = encode_constraints(msg, life, confirm, maxsign, 521 provider)) != 0) 522 goto out; 523 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 524 goto out; 525 if ((r = sshbuf_get_u8(msg, &type)) != 0) 526 goto out; 527 r = decode_reply(type); 528 out: 529 sshbuf_free(msg); 530 return r; 531 } 532 533 /* 534 * Removes an identity from the authentication server. 535 * This call is intended only for use by ssh-add(1) and like applications. 536 */ 537 int 538 ssh_remove_identity(int sock, struct sshkey *key) 539 { 540 struct sshbuf *msg; 541 int r; 542 u_char type, *blob = NULL; 543 size_t blen; 544 545 if ((msg = sshbuf_new()) == NULL) 546 return SSH_ERR_ALLOC_FAIL; 547 548 if (key->type != KEY_UNSPEC) { 549 if ((r = sshkey_to_blob(key, &blob, &blen)) != 0) 550 goto out; 551 if ((r = sshbuf_put_u8(msg, 552 SSH2_AGENTC_REMOVE_IDENTITY)) != 0 || 553 (r = sshbuf_put_string(msg, blob, blen)) != 0) 554 goto out; 555 } else { 556 r = SSH_ERR_INVALID_ARGUMENT; 557 goto out; 558 } 559 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 560 goto out; 561 if ((r = sshbuf_get_u8(msg, &type)) != 0) 562 goto out; 563 r = decode_reply(type); 564 out: 565 if (blob != NULL) 566 freezero(blob, blen); 567 sshbuf_free(msg); 568 return r; 569 } 570 571 /* 572 * Add/remove an token-based identity from the authentication server. 573 * This call is intended only for use by ssh-add(1) and like applications. 574 */ 575 int 576 ssh_update_card(int sock, int add, const char *reader_id, const char *pin, 577 u_int life, u_int confirm) 578 { 579 struct sshbuf *msg; 580 int r, constrained = (life || confirm); 581 u_char type; 582 583 if (add) { 584 type = constrained ? 585 SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED : 586 SSH_AGENTC_ADD_SMARTCARD_KEY; 587 } else 588 type = SSH_AGENTC_REMOVE_SMARTCARD_KEY; 589 590 if ((msg = sshbuf_new()) == NULL) 591 return SSH_ERR_ALLOC_FAIL; 592 if ((r = sshbuf_put_u8(msg, type)) != 0 || 593 (r = sshbuf_put_cstring(msg, reader_id)) != 0 || 594 (r = sshbuf_put_cstring(msg, pin)) != 0) 595 goto out; 596 if (constrained && 597 (r = encode_constraints(msg, life, confirm, 0, NULL)) != 0) 598 goto out; 599 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 600 goto out; 601 if ((r = sshbuf_get_u8(msg, &type)) != 0) 602 goto out; 603 r = decode_reply(type); 604 out: 605 sshbuf_free(msg); 606 return r; 607 } 608 609 /* 610 * Removes all identities from the agent. 611 * This call is intended only for use by ssh-add(1) and like applications. 612 * 613 * This supports the SSH protocol 1 message to because, when clearing all 614 * keys from an agent, we generally want to clear both protocol v1 and v2 615 * keys. 616 */ 617 int 618 ssh_remove_all_identities(int sock, int version) 619 { 620 struct sshbuf *msg; 621 u_char type = (version == 1) ? 622 SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES : 623 SSH2_AGENTC_REMOVE_ALL_IDENTITIES; 624 int r; 625 626 if ((msg = sshbuf_new()) == NULL) 627 return SSH_ERR_ALLOC_FAIL; 628 if ((r = sshbuf_put_u8(msg, type)) != 0) 629 goto out; 630 if ((r = ssh_request_reply(sock, msg, msg)) != 0) 631 goto out; 632 if ((r = sshbuf_get_u8(msg, &type)) != 0) 633 goto out; 634 r = decode_reply(type); 635 out: 636 sshbuf_free(msg); 637 return r; 638 } 639