diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-10-31 21:22:01 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2019-11-01 09:46:09 +1100 |
commit | 07da39f71d36fb547749a5b16aa8892e621a7e4a (patch) | |
tree | dd75cbd723102d887bc11f781cc0a23eee6b2f2f /ssh-agent.c | |
parent | eebec620c9519c4839d781c4d5b6082152998f82 (diff) |
upstream: ssh-agent support for U2F/FIDO keys
feedback & ok markus@
OpenBSD-Commit-ID: bb544a44bc32e45d2ec8bf652db2046f38360acb
Diffstat (limited to 'ssh-agent.c')
-rw-r--r-- | ssh-agent.c | 218 |
1 files changed, 199 insertions, 19 deletions
diff --git a/ssh-agent.c b/ssh-agent.c index e500591a9..6bf9536fb 100644 --- a/ssh-agent.c +++ b/ssh-agent.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh-agent.c,v 1.237 2019/06/28 13:35:04 deraadt Exp $ */ | 1 | /* $OpenBSD: ssh-agent.c,v 1.238 2019/10/31 21:22:01 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -41,6 +41,7 @@ | |||
41 | #include <sys/resource.h> | 41 | #include <sys/resource.h> |
42 | #include <sys/stat.h> | 42 | #include <sys/stat.h> |
43 | #include <sys/socket.h> | 43 | #include <sys/socket.h> |
44 | #include <sys/wait.h> | ||
44 | #ifdef HAVE_SYS_TIME_H | 45 | #ifdef HAVE_SYS_TIME_H |
45 | # include <sys/time.h> | 46 | # include <sys/time.h> |
46 | #endif | 47 | #endif |
@@ -85,13 +86,13 @@ | |||
85 | #include "digest.h" | 86 | #include "digest.h" |
86 | #include "ssherr.h" | 87 | #include "ssherr.h" |
87 | #include "match.h" | 88 | #include "match.h" |
88 | 89 | #include "msg.h" | |
89 | #ifdef ENABLE_PKCS11 | 90 | #include "pathnames.h" |
90 | #include "ssh-pkcs11.h" | 91 | #include "ssh-pkcs11.h" |
91 | #endif | 92 | #include "ssh-sk.h" |
92 | 93 | ||
93 | #ifndef DEFAULT_PKCS11_WHITELIST | 94 | #ifndef DEFAULT_PROVIDER_WHITELIST |
94 | # define DEFAULT_PKCS11_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" | 95 | # define DEFAULT_PROVIDER_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" |
95 | #endif | 96 | #endif |
96 | 97 | ||
97 | /* Maximum accepted message length */ | 98 | /* Maximum accepted message length */ |
@@ -123,6 +124,7 @@ typedef struct identity { | |||
123 | char *provider; | 124 | char *provider; |
124 | time_t death; | 125 | time_t death; |
125 | u_int confirm; | 126 | u_int confirm; |
127 | char *sk_provider; | ||
126 | } Identity; | 128 | } Identity; |
127 | 129 | ||
128 | struct idtable { | 130 | struct idtable { |
@@ -146,8 +148,8 @@ pid_t cleanup_pid = 0; | |||
146 | char socket_name[PATH_MAX]; | 148 | char socket_name[PATH_MAX]; |
147 | char socket_dir[PATH_MAX]; | 149 | char socket_dir[PATH_MAX]; |
148 | 150 | ||
149 | /* PKCS#11 path whitelist */ | 151 | /* PKCS#11/Security key path whitelist */ |
150 | static char *pkcs11_whitelist; | 152 | static char *provider_whitelist; |
151 | 153 | ||
152 | /* locking */ | 154 | /* locking */ |
153 | #define LOCK_SIZE 32 | 155 | #define LOCK_SIZE 32 |
@@ -189,6 +191,7 @@ free_identity(Identity *id) | |||
189 | sshkey_free(id->key); | 191 | sshkey_free(id->key); |
190 | free(id->provider); | 192 | free(id->provider); |
191 | free(id->comment); | 193 | free(id->comment); |
194 | free(id->sk_provider); | ||
192 | free(id); | 195 | free(id); |
193 | } | 196 | } |
194 | 197 | ||
@@ -278,6 +281,121 @@ agent_decode_alg(struct sshkey *key, u_int flags) | |||
278 | return NULL; | 281 | return NULL; |
279 | } | 282 | } |
280 | 283 | ||
284 | static int | ||
285 | provider_sign(const char *provider, struct sshkey *key, | ||
286 | u_char **sigp, size_t *lenp, | ||
287 | const u_char *data, size_t datalen, | ||
288 | const char *alg, u_int compat) | ||
289 | { | ||
290 | int status, pair[2], r = SSH_ERR_INTERNAL_ERROR; | ||
291 | pid_t pid; | ||
292 | char *helper, *verbosity = NULL; | ||
293 | struct sshbuf *kbuf, *req, *resp; | ||
294 | u_char version; | ||
295 | |||
296 | debug3("%s: start for provider %s", __func__, provider); | ||
297 | |||
298 | *sigp = NULL; | ||
299 | *lenp = 0; | ||
300 | |||
301 | helper = getenv("SSH_SK_HELPER"); | ||
302 | if (helper == NULL || strlen(helper) == 0) | ||
303 | helper = _PATH_SSH_SK_HELPER; | ||
304 | if (log_level_get() >= SYSLOG_LEVEL_DEBUG1) | ||
305 | verbosity = "-vvv"; | ||
306 | |||
307 | /* Start helper */ | ||
308 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { | ||
309 | error("socketpair: %s", strerror(errno)); | ||
310 | return SSH_ERR_SYSTEM_ERROR; | ||
311 | } | ||
312 | if ((pid = fork()) == -1) { | ||
313 | error("fork: %s", strerror(errno)); | ||
314 | close(pair[0]); | ||
315 | close(pair[1]); | ||
316 | return SSH_ERR_SYSTEM_ERROR; | ||
317 | } | ||
318 | if (pid == 0) { | ||
319 | if ((dup2(pair[1], STDIN_FILENO) == -1) || | ||
320 | (dup2(pair[1], STDOUT_FILENO) == -1)) | ||
321 | fatal("%s: dup2: %s", __func__, ssh_err(r)); | ||
322 | close(pair[0]); | ||
323 | close(pair[1]); | ||
324 | closefrom(STDERR_FILENO + 1); | ||
325 | debug("%s: starting %s %s", __func__, helper, | ||
326 | verbosity == NULL ? "" : verbosity); | ||
327 | execlp(helper, helper, verbosity, (char *)NULL); | ||
328 | fatal("%s: execlp: %s", __func__, strerror(errno)); | ||
329 | } | ||
330 | close(pair[1]); | ||
331 | |||
332 | if ((kbuf = sshbuf_new()) == NULL || | ||
333 | (req = sshbuf_new()) == NULL || | ||
334 | (resp = sshbuf_new()) == NULL) | ||
335 | fatal("%s: sshbuf_new failed", __func__); | ||
336 | |||
337 | if ((r = sshkey_private_serialize(key, kbuf)) != 0 || | ||
338 | (r = sshbuf_put_stringb(req, kbuf)) != 0 || | ||
339 | (r = sshbuf_put_cstring(req, provider)) != 0 || | ||
340 | (r = sshbuf_put_string(req, data, datalen)) != 0 || | ||
341 | (r = sshbuf_put_u32(req, compat)) != 0) | ||
342 | fatal("%s: compose: %s", __func__, ssh_err(r)); | ||
343 | if ((r = ssh_msg_send(pair[0], SSH_SK_HELPER_VERSION, req)) != 0) { | ||
344 | error("%s: send: %s", __func__, ssh_err(r)); | ||
345 | goto out; | ||
346 | } | ||
347 | if ((r = ssh_msg_recv(pair[0], resp)) != 0) { | ||
348 | error("%s: receive: %s", __func__, ssh_err(r)); | ||
349 | goto out; | ||
350 | } | ||
351 | if ((r = sshbuf_get_u8(resp, &version)) != 0) { | ||
352 | error("%s: parse version: %s", __func__, ssh_err(r)); | ||
353 | goto out; | ||
354 | } | ||
355 | if (version != SSH_SK_HELPER_VERSION) { | ||
356 | error("%s: unsupported version: got %u, expected %u", | ||
357 | __func__, version, SSH_SK_HELPER_VERSION); | ||
358 | r = SSH_ERR_INVALID_FORMAT; | ||
359 | goto out; | ||
360 | } | ||
361 | if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) { | ||
362 | error("%s: parse signature: %s", __func__, ssh_err(r)); | ||
363 | r = SSH_ERR_INVALID_FORMAT; | ||
364 | goto out; | ||
365 | } | ||
366 | if (sshbuf_len(resp) != 0) { | ||
367 | error("%s: trailing data in response", __func__); | ||
368 | r = SSH_ERR_INVALID_FORMAT; | ||
369 | goto out; | ||
370 | } | ||
371 | /* success */ | ||
372 | r = 0; | ||
373 | out: | ||
374 | while (waitpid(pid, &status, 0) == -1) { | ||
375 | if (errno != EINTR) | ||
376 | fatal("%s: waitpid: %s", __func__, ssh_err(r)); | ||
377 | } | ||
378 | if (!WIFEXITED(status)) { | ||
379 | error("%s: helper %s exited abnormally", __func__, helper); | ||
380 | if (r == 0) | ||
381 | r = SSH_ERR_SYSTEM_ERROR; | ||
382 | } else if (WEXITSTATUS(status) != 0) { | ||
383 | error("%s: helper %s exited with non-zero exit status", | ||
384 | __func__, helper); | ||
385 | if (r == 0) | ||
386 | r = SSH_ERR_SYSTEM_ERROR; | ||
387 | } | ||
388 | if (r != 0) { | ||
389 | freezero(*sigp, *lenp); | ||
390 | *sigp = NULL; | ||
391 | *lenp = 0; | ||
392 | } | ||
393 | sshbuf_free(kbuf); | ||
394 | sshbuf_free(req); | ||
395 | sshbuf_free(resp); | ||
396 | return r; | ||
397 | } | ||
398 | |||
281 | /* ssh2 only */ | 399 | /* ssh2 only */ |
282 | static void | 400 | static void |
283 | process_sign_request2(SocketEntry *e) | 401 | process_sign_request2(SocketEntry *e) |
@@ -308,10 +426,19 @@ process_sign_request2(SocketEntry *e) | |||
308 | verbose("%s: user refused key", __func__); | 426 | verbose("%s: user refused key", __func__); |
309 | goto send; | 427 | goto send; |
310 | } | 428 | } |
311 | if ((r = sshkey_sign(id->key, &signature, &slen, | 429 | if (id->sk_provider != NULL) { |
312 | data, dlen, agent_decode_alg(key, flags), compat)) != 0) { | 430 | if ((r = provider_sign(id->sk_provider, id->key, &signature, |
313 | error("%s: sshkey_sign: %s", __func__, ssh_err(r)); | 431 | &slen, data, dlen, agent_decode_alg(key, flags), |
314 | goto send; | 432 | compat)) != 0) { |
433 | error("%s: sshkey_sign: %s", __func__, ssh_err(r)); | ||
434 | goto send; | ||
435 | } | ||
436 | } else { | ||
437 | if ((r = sshkey_sign(id->key, &signature, &slen, | ||
438 | data, dlen, agent_decode_alg(key, flags), compat)) != 0) { | ||
439 | error("%s: sshkey_sign: %s", __func__, ssh_err(r)); | ||
440 | goto send; | ||
441 | } | ||
315 | } | 442 | } |
316 | /* Success */ | 443 | /* Success */ |
317 | ok = 0; | 444 | ok = 0; |
@@ -411,7 +538,7 @@ process_add_identity(SocketEntry *e) | |||
411 | Identity *id; | 538 | Identity *id; |
412 | int success = 0, confirm = 0; | 539 | int success = 0, confirm = 0; |
413 | u_int seconds, maxsign; | 540 | u_int seconds, maxsign; |
414 | char *comment = NULL; | 541 | char *fp, *comment = NULL, *ext_name = NULL, *sk_provider = NULL; |
415 | time_t death = 0; | 542 | time_t death = 0; |
416 | struct sshkey *k = NULL; | 543 | struct sshkey *k = NULL; |
417 | u_char ctype; | 544 | u_char ctype; |
@@ -456,15 +583,58 @@ process_add_identity(SocketEntry *e) | |||
456 | goto err; | 583 | goto err; |
457 | } | 584 | } |
458 | break; | 585 | break; |
586 | case SSH_AGENT_CONSTRAIN_EXTENSION: | ||
587 | if ((r = sshbuf_get_cstring(e->request, | ||
588 | &ext_name, NULL)) != 0) { | ||
589 | error("%s: cannot parse extension: %s", | ||
590 | __func__, ssh_err(r)); | ||
591 | goto err; | ||
592 | } | ||
593 | debug("%s: constraint ext %s", __func__, ext_name); | ||
594 | if (strcmp(ext_name, "sk-provider@openssh.com") == 0) { | ||
595 | if (sk_provider != NULL) { | ||
596 | error("%s already set", ext_name); | ||
597 | goto err; | ||
598 | } | ||
599 | if ((r = sshbuf_get_cstring(e->request, | ||
600 | &sk_provider, NULL)) != 0) { | ||
601 | error("%s: cannot parse %s: %s", | ||
602 | __func__, ext_name, ssh_err(r)); | ||
603 | goto err; | ||
604 | } | ||
605 | } else { | ||
606 | error("%s: unsupported constraint \"%s\"", | ||
607 | __func__, ext_name); | ||
608 | goto err; | ||
609 | } | ||
610 | free(ext_name); | ||
611 | break; | ||
459 | default: | 612 | default: |
460 | error("%s: Unknown constraint %d", __func__, ctype); | 613 | error("%s: Unknown constraint %d", __func__, ctype); |
461 | err: | 614 | err: |
615 | free(sk_provider); | ||
616 | free(ext_name); | ||
462 | sshbuf_reset(e->request); | 617 | sshbuf_reset(e->request); |
463 | free(comment); | 618 | free(comment); |
464 | sshkey_free(k); | 619 | sshkey_free(k); |
465 | goto send; | 620 | goto send; |
466 | } | 621 | } |
467 | } | 622 | } |
623 | if (sk_provider != NULL) { | ||
624 | if (sshkey_type_plain(k->type) != KEY_ECDSA_SK) { | ||
625 | error("Cannot add provider: %s is not a security key", | ||
626 | sshkey_type(k)); | ||
627 | free(sk_provider); | ||
628 | goto send; | ||
629 | } | ||
630 | if (match_pattern_list(sk_provider, | ||
631 | provider_whitelist, 0) != 1) { | ||
632 | error("Refusing add key: provider %s not whitelisted", | ||
633 | sk_provider); | ||
634 | free(sk_provider); | ||
635 | goto send; | ||
636 | } | ||
637 | } | ||
468 | 638 | ||
469 | success = 1; | 639 | success = 1; |
470 | if (lifetime && !death) | 640 | if (lifetime && !death) |
@@ -478,11 +648,21 @@ process_add_identity(SocketEntry *e) | |||
478 | /* key state might have been updated */ | 648 | /* key state might have been updated */ |
479 | sshkey_free(id->key); | 649 | sshkey_free(id->key); |
480 | free(id->comment); | 650 | free(id->comment); |
651 | free(id->sk_provider); | ||
481 | } | 652 | } |
482 | id->key = k; | 653 | id->key = k; |
483 | id->comment = comment; | 654 | id->comment = comment; |
484 | id->death = death; | 655 | id->death = death; |
485 | id->confirm = confirm; | 656 | id->confirm = confirm; |
657 | id->sk_provider = sk_provider; | ||
658 | |||
659 | if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, | ||
660 | SSH_FP_DEFAULT)) == NULL) | ||
661 | fatal("%s: sshkey_fingerprint failed", __func__); | ||
662 | debug("%s: add %s %s \"%.100s\" (life: %u) (confirm: %u) " | ||
663 | "(provider: %s)", __func__, sshkey_ssh_name(k), fp, comment, | ||
664 | seconds, confirm, sk_provider == NULL ? "none" : sk_provider); | ||
665 | free(fp); | ||
486 | send: | 666 | send: |
487 | send_status(e, success); | 667 | send_status(e, success); |
488 | } | 668 | } |
@@ -600,7 +780,7 @@ process_add_smartcard_key(SocketEntry *e) | |||
600 | provider, strerror(errno)); | 780 | provider, strerror(errno)); |
601 | goto send; | 781 | goto send; |
602 | } | 782 | } |
603 | if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) { | 783 | if (match_pattern_list(canonical_provider, provider_whitelist, 0) != 1) { |
604 | verbose("refusing PKCS#11 add of \"%.100s\": " | 784 | verbose("refusing PKCS#11 add of \"%.100s\": " |
605 | "provider not whitelisted", canonical_provider); | 785 | "provider not whitelisted", canonical_provider); |
606 | goto send; | 786 | goto send; |
@@ -1079,7 +1259,7 @@ usage(void) | |||
1079 | { | 1259 | { |
1080 | fprintf(stderr, | 1260 | fprintf(stderr, |
1081 | "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" | 1261 | "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
1082 | " [-P pkcs11_whitelist] [-t life] [command [arg ...]]\n" | 1262 | " [-P provider_whitelist] [-t life] [command [arg ...]]\n" |
1083 | " ssh-agent [-c | -s] -k\n"); | 1263 | " ssh-agent [-c | -s] -k\n"); |
1084 | exit(1); | 1264 | exit(1); |
1085 | } | 1265 | } |
@@ -1137,9 +1317,9 @@ main(int ac, char **av) | |||
1137 | k_flag++; | 1317 | k_flag++; |
1138 | break; | 1318 | break; |
1139 | case 'P': | 1319 | case 'P': |
1140 | if (pkcs11_whitelist != NULL) | 1320 | if (provider_whitelist != NULL) |
1141 | fatal("-P option already specified"); | 1321 | fatal("-P option already specified"); |
1142 | pkcs11_whitelist = xstrdup(optarg); | 1322 | provider_whitelist = xstrdup(optarg); |
1143 | break; | 1323 | break; |
1144 | case 's': | 1324 | case 's': |
1145 | if (c_flag) | 1325 | if (c_flag) |
@@ -1175,8 +1355,8 @@ main(int ac, char **av) | |||
1175 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) | 1355 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
1176 | usage(); | 1356 | usage(); |
1177 | 1357 | ||
1178 | if (pkcs11_whitelist == NULL) | 1358 | if (provider_whitelist == NULL) |
1179 | pkcs11_whitelist = xstrdup(DEFAULT_PKCS11_WHITELIST); | 1359 | provider_whitelist = xstrdup(DEFAULT_PROVIDER_WHITELIST); |
1180 | 1360 | ||
1181 | if (ac == 0 && !c_flag && !s_flag) { | 1361 | if (ac == 0 && !c_flag && !s_flag) { |
1182 | shell = getenv("SHELL"); | 1362 | shell = getenv("SHELL"); |