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