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