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 <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 struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL; 234 235 *sigp = NULL; 236 *lenp = 0; 237 238 if ((kbuf = sshbuf_new()) == NULL || 239 (req = sshbuf_new()) == NULL) { 240 r = SSH_ERR_ALLOC_FAIL; 241 goto out; 242 } 243 244 if ((r = sshkey_private_serialize(key, kbuf)) != 0) { 245 error_fr(r, "encode key"); 246 goto out; 247 } 248 if ((r = sshbuf_put_stringb(req, kbuf)) != 0 || 249 (r = sshbuf_put_cstring(req, provider)) != 0 || 250 (r = sshbuf_put_string(req, data, datalen)) != 0 || 251 (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */ 252 (r = sshbuf_put_u32(req, compat)) != 0 || 253 (r = sshbuf_put_cstring(req, pin)) != 0) { 254 error_fr(r, "compose"); 255 goto out; 256 } 257 258 if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0) 259 goto out; 260 261 if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) { 262 error_fr(r, "parse signature"); 263 r = SSH_ERR_INVALID_FORMAT; 264 goto out; 265 } 266 if (sshbuf_len(resp) != 0) { 267 error_f("trailing data in response"); 268 r = SSH_ERR_INVALID_FORMAT; 269 goto out; 270 } 271 /* success */ 272 r = 0; 273 out: 274 oerrno = errno; 275 if (r != 0) { 276 freezero(*sigp, *lenp); 277 *sigp = NULL; 278 *lenp = 0; 279 } 280 sshbuf_free(kbuf); 281 sshbuf_free(req); 282 sshbuf_free(resp); 283 errno = oerrno; 284 return r; 285 } 286 287 int 288 sshsk_enroll(int type, const char *provider_path, const char *device, 289 const char *application, const char *userid, uint8_t flags, 290 const char *pin, struct sshbuf *challenge_buf, 291 struct sshkey **keyp, struct sshbuf *attest) 292 { 293 int oerrno, r = SSH_ERR_INTERNAL_ERROR; 294 struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL; 295 struct sshkey *key = NULL; 296 297 *keyp = NULL; 298 if (attest != NULL) 299 sshbuf_reset(attest); 300 301 if (type < 0) 302 return SSH_ERR_INVALID_ARGUMENT; 303 304 if ((abuf = sshbuf_new()) == NULL || 305 (kbuf = sshbuf_new()) == NULL || 306 (req = sshbuf_new()) == NULL) { 307 r = SSH_ERR_ALLOC_FAIL; 308 goto out; 309 } 310 311 if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 || 312 (r = sshbuf_put_cstring(req, provider_path)) != 0 || 313 (r = sshbuf_put_cstring(req, device)) != 0 || 314 (r = sshbuf_put_cstring(req, application)) != 0 || 315 (r = sshbuf_put_cstring(req, userid)) != 0 || 316 (r = sshbuf_put_u8(req, flags)) != 0 || 317 (r = sshbuf_put_cstring(req, pin)) != 0 || 318 (r = sshbuf_put_stringb(req, challenge_buf)) != 0) { 319 error_fr(r, "compose"); 320 goto out; 321 } 322 323 if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0) 324 goto out; 325 326 if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 || 327 (r = sshbuf_get_stringb(resp, abuf)) != 0) { 328 error_fr(r, "parse"); 329 r = SSH_ERR_INVALID_FORMAT; 330 goto out; 331 } 332 if (sshbuf_len(resp) != 0) { 333 error_f("trailing data in response"); 334 r = SSH_ERR_INVALID_FORMAT; 335 goto out; 336 } 337 if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) { 338 error_fr(r, "encode"); 339 goto out; 340 } 341 if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) { 342 error_fr(r, "encode attestation information"); 343 goto out; 344 } 345 346 /* success */ 347 r = 0; 348 *keyp = key; 349 key = NULL; 350 out: 351 oerrno = errno; 352 sshkey_free(key); 353 sshbuf_free(kbuf); 354 sshbuf_free(abuf); 355 sshbuf_free(req); 356 sshbuf_free(resp); 357 errno = oerrno; 358 return r; 359 } 360 361 static void 362 sshsk_free_resident_key(struct sshsk_resident_key *srk) 363 { 364 if (srk == NULL) 365 return; 366 sshkey_free(srk->key); 367 freezero(srk->user_id, srk->user_id_len); 368 free(srk); 369 } 370 371 372 void 373 sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks) 374 { 375 size_t i; 376 377 if (srks == NULL || nsrks == 0) 378 return; 379 380 for (i = 0; i < nsrks; i++) 381 sshsk_free_resident_key(srks[i]); 382 free(srks); 383 } 384 385 int 386 sshsk_load_resident(const char *provider_path, const char *device, 387 const char *pin, u_int flags, struct sshsk_resident_key ***srksp, 388 size_t *nsrksp) 389 { 390 int oerrno, r = SSH_ERR_INTERNAL_ERROR; 391 struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL; 392 struct sshkey *key = NULL; 393 struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp; 394 u_char *userid = NULL; 395 size_t userid_len = 0, nsrks = 0; 396 397 *srksp = NULL; 398 *nsrksp = 0; 399 400 if ((kbuf = sshbuf_new()) == NULL || 401 (req = sshbuf_new()) == NULL) { 402 r = SSH_ERR_ALLOC_FAIL; 403 goto out; 404 } 405 406 if ((r = sshbuf_put_cstring(req, provider_path)) != 0 || 407 (r = sshbuf_put_cstring(req, device)) != 0 || 408 (r = sshbuf_put_cstring(req, pin)) != 0 || 409 (r = sshbuf_put_u32(req, flags)) != 0) { 410 error_fr(r, "compose"); 411 goto out; 412 } 413 414 if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0) 415 goto out; 416 417 while (sshbuf_len(resp) != 0) { 418 /* key, comment, user_id */ 419 if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 || 420 (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0 || 421 (r = sshbuf_get_string(resp, &userid, &userid_len)) != 0) { 422 error_fr(r, "parse"); 423 r = SSH_ERR_INVALID_FORMAT; 424 goto out; 425 } 426 if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) { 427 error_fr(r, "decode key"); 428 goto out; 429 } 430 if ((srk = calloc(1, sizeof(*srk))) == NULL) { 431 error_f("calloc failed"); 432 goto out; 433 } 434 srk->key = key; 435 key = NULL; 436 srk->user_id = userid; 437 srk->user_id_len = userid_len; 438 userid = NULL; 439 userid_len = 0; 440 if ((tmp = recallocarray(srks, nsrks, nsrks + 1, 441 sizeof(*srks))) == NULL) { 442 error_f("recallocarray keys failed"); 443 goto out; 444 } 445 debug_f("srks[%zu]: %s %s uidlen %zu", nsrks, 446 sshkey_type(srk->key), srk->key->sk_application, 447 srk->user_id_len); 448 srks = tmp; 449 srks[nsrks++] = srk; 450 srk = NULL; 451 } 452 453 /* success */ 454 r = 0; 455 *srksp = srks; 456 *nsrksp = nsrks; 457 srks = NULL; 458 nsrks = 0; 459 out: 460 oerrno = errno; 461 sshsk_free_resident_key(srk); 462 sshsk_free_resident_keys(srks, nsrks); 463 freezero(userid, userid_len); 464 sshkey_free(key); 465 sshbuf_free(kbuf); 466 sshbuf_free(req); 467 sshbuf_free(resp); 468 errno = oerrno; 469 return r; 470 } 471