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