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