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