diff options
Diffstat (limited to 'ssh-agent.c')
-rw-r--r-- | ssh-agent.c | 158 |
1 files changed, 129 insertions, 29 deletions
diff --git a/ssh-agent.c b/ssh-agent.c index e081413b8..e1fd1f3f6 100644 --- a/ssh-agent.c +++ b/ssh-agent.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh-agent.c,v 1.257 2020/03/06 18:28:27 markus Exp $ */ | 1 | /* $OpenBSD: ssh-agent.c,v 1.264 2020/09/18 08:16:38 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 |
@@ -77,6 +77,7 @@ | |||
77 | 77 | ||
78 | #include "xmalloc.h" | 78 | #include "xmalloc.h" |
79 | #include "ssh.h" | 79 | #include "ssh.h" |
80 | #include "ssh2.h" | ||
80 | #include "sshbuf.h" | 81 | #include "sshbuf.h" |
81 | #include "sshkey.h" | 82 | #include "sshkey.h" |
82 | #include "authfd.h" | 83 | #include "authfd.h" |
@@ -92,8 +93,8 @@ | |||
92 | #include "ssh-pkcs11.h" | 93 | #include "ssh-pkcs11.h" |
93 | #include "sk-api.h" | 94 | #include "sk-api.h" |
94 | 95 | ||
95 | #ifndef DEFAULT_PROVIDER_WHITELIST | 96 | #ifndef DEFAULT_ALLOWED_PROVIDERS |
96 | # define DEFAULT_PROVIDER_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" | 97 | # define DEFAULT_ALLOWED_PROVIDERS "/usr/lib*/*,/usr/local/lib*/*" |
97 | #endif | 98 | #endif |
98 | 99 | ||
99 | /* Maximum accepted message length */ | 100 | /* Maximum accepted message length */ |
@@ -149,8 +150,8 @@ pid_t cleanup_pid = 0; | |||
149 | char socket_name[PATH_MAX]; | 150 | char socket_name[PATH_MAX]; |
150 | char socket_dir[PATH_MAX]; | 151 | char socket_dir[PATH_MAX]; |
151 | 152 | ||
152 | /* PKCS#11/Security key path whitelist */ | 153 | /* Pattern-list of allowed PKCS#11/Security key paths */ |
153 | static char *provider_whitelist; | 154 | static char *allowed_providers; |
154 | 155 | ||
155 | /* locking */ | 156 | /* locking */ |
156 | #define LOCK_SIZE 32 | 157 | #define LOCK_SIZE 32 |
@@ -167,6 +168,9 @@ static long lifetime = 0; | |||
167 | 168 | ||
168 | static int fingerprint_hash = SSH_FP_HASH_DEFAULT; | 169 | static int fingerprint_hash = SSH_FP_HASH_DEFAULT; |
169 | 170 | ||
171 | /* Refuse signing of non-SSH messages for web-origin FIDO keys */ | ||
172 | static int restrict_websafe = 1; | ||
173 | |||
170 | static void | 174 | static void |
171 | close_socket(SocketEntry *e) | 175 | close_socket(SocketEntry *e) |
172 | { | 176 | { |
@@ -282,6 +286,80 @@ agent_decode_alg(struct sshkey *key, u_int flags) | |||
282 | return NULL; | 286 | return NULL; |
283 | } | 287 | } |
284 | 288 | ||
289 | /* | ||
290 | * This function inspects a message to be signed by a FIDO key that has a | ||
291 | * web-like application string (i.e. one that does not begin with "ssh:". | ||
292 | * It checks that the message is one of those expected for SSH operations | ||
293 | * (pubkey userauth, sshsig, CA key signing) to exclude signing challenges | ||
294 | * for the web. | ||
295 | */ | ||
296 | static int | ||
297 | check_websafe_message_contents(struct sshkey *key, | ||
298 | const u_char *msg, size_t len) | ||
299 | { | ||
300 | int matched = 0; | ||
301 | struct sshbuf *b; | ||
302 | u_char m, n; | ||
303 | char *cp1 = NULL, *cp2 = NULL; | ||
304 | int r; | ||
305 | struct sshkey *mkey = NULL; | ||
306 | |||
307 | if ((b = sshbuf_from(msg, len)) == NULL) | ||
308 | fatal("%s: sshbuf_new", __func__); | ||
309 | |||
310 | /* SSH userauth request */ | ||
311 | if ((r = sshbuf_get_string_direct(b, NULL, NULL)) == 0 && /* sess_id */ | ||
312 | (r = sshbuf_get_u8(b, &m)) == 0 && /* SSH2_MSG_USERAUTH_REQUEST */ | ||
313 | (r = sshbuf_get_cstring(b, NULL, NULL)) == 0 && /* server user */ | ||
314 | (r = sshbuf_get_cstring(b, &cp1, NULL)) == 0 && /* service */ | ||
315 | (r = sshbuf_get_cstring(b, &cp2, NULL)) == 0 && /* method */ | ||
316 | (r = sshbuf_get_u8(b, &n)) == 0 && /* sig-follows */ | ||
317 | (r = sshbuf_get_cstring(b, NULL, NULL)) == 0 && /* alg */ | ||
318 | (r = sshkey_froms(b, &mkey)) == 0 && /* key */ | ||
319 | sshbuf_len(b) == 0) { | ||
320 | debug("%s: parsed userauth", __func__); | ||
321 | if (m == SSH2_MSG_USERAUTH_REQUEST && n == 1 && | ||
322 | strcmp(cp1, "ssh-connection") == 0 && | ||
323 | strcmp(cp2, "publickey") == 0 && | ||
324 | sshkey_equal(key, mkey)) { | ||
325 | debug("%s: well formed userauth", __func__); | ||
326 | matched = 1; | ||
327 | } | ||
328 | } | ||
329 | free(cp1); | ||
330 | free(cp2); | ||
331 | sshkey_free(mkey); | ||
332 | sshbuf_free(b); | ||
333 | if (matched) | ||
334 | return 1; | ||
335 | |||
336 | if ((b = sshbuf_from(msg, len)) == NULL) | ||
337 | fatal("%s: sshbuf_new", __func__); | ||
338 | cp1 = cp2 = NULL; | ||
339 | mkey = NULL; | ||
340 | |||
341 | /* SSHSIG */ | ||
342 | if ((r = sshbuf_cmp(b, 0, "SSHSIG", 6)) == 0 && | ||
343 | (r = sshbuf_consume(b, 6)) == 0 && | ||
344 | (r = sshbuf_get_cstring(b, NULL, NULL)) == 0 && /* namespace */ | ||
345 | (r = sshbuf_get_string_direct(b, NULL, NULL)) == 0 && /* reserved */ | ||
346 | (r = sshbuf_get_cstring(b, NULL, NULL)) == 0 && /* hashalg */ | ||
347 | (r = sshbuf_get_string_direct(b, NULL, NULL)) == 0 && /* H(msg) */ | ||
348 | sshbuf_len(b) == 0) { | ||
349 | debug("%s: parsed sshsig", __func__); | ||
350 | matched = 1; | ||
351 | } | ||
352 | |||
353 | sshbuf_free(b); | ||
354 | if (matched) | ||
355 | return 1; | ||
356 | |||
357 | /* XXX CA signature operation */ | ||
358 | |||
359 | error("web-origin key attempting to sign non-SSH message"); | ||
360 | return 0; | ||
361 | } | ||
362 | |||
285 | /* ssh2 only */ | 363 | /* ssh2 only */ |
286 | static void | 364 | static void |
287 | process_sign_request2(SocketEntry *e) | 365 | process_sign_request2(SocketEntry *e) |
@@ -314,18 +392,25 @@ process_sign_request2(SocketEntry *e) | |||
314 | verbose("%s: user refused key", __func__); | 392 | verbose("%s: user refused key", __func__); |
315 | goto send; | 393 | goto send; |
316 | } | 394 | } |
317 | if (sshkey_is_sk(id->key) && | 395 | if (sshkey_is_sk(id->key)) { |
318 | (id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD)) { | 396 | if (strncmp(id->key->sk_application, "ssh:", 4) != 0 && |
319 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, | 397 | !check_websafe_message_contents(key, data, dlen)) { |
320 | SSH_FP_DEFAULT)) == NULL) | 398 | /* error already logged */ |
321 | fatal("%s: fingerprint failed", __func__); | 399 | goto send; |
322 | notifier = notify_start(0, | 400 | } |
323 | "Confirm user presence for key %s %s", | 401 | if ((id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD)) { |
324 | sshkey_type(id->key), fp); | 402 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
403 | SSH_FP_DEFAULT)) == NULL) | ||
404 | fatal("%s: fingerprint failed", __func__); | ||
405 | notifier = notify_start(0, | ||
406 | "Confirm user presence for key %s %s", | ||
407 | sshkey_type(id->key), fp); | ||
408 | } | ||
325 | } | 409 | } |
410 | /* XXX support PIN required FIDO keys */ | ||
326 | if ((r = sshkey_sign(id->key, &signature, &slen, | 411 | if ((r = sshkey_sign(id->key, &signature, &slen, |
327 | data, dlen, agent_decode_alg(key, flags), | 412 | data, dlen, agent_decode_alg(key, flags), |
328 | id->sk_provider, compat)) != 0) { | 413 | id->sk_provider, NULL, compat)) != 0) { |
329 | error("%s: sshkey_sign: %s", __func__, ssh_err(r)); | 414 | error("%s: sshkey_sign: %s", __func__, ssh_err(r)); |
330 | goto send; | 415 | goto send; |
331 | } | 416 | } |
@@ -528,9 +613,9 @@ process_add_identity(SocketEntry *e) | |||
528 | free(sk_provider); | 613 | free(sk_provider); |
529 | sk_provider = xstrdup(canonical_provider); | 614 | sk_provider = xstrdup(canonical_provider); |
530 | if (match_pattern_list(sk_provider, | 615 | if (match_pattern_list(sk_provider, |
531 | provider_whitelist, 0) != 1) { | 616 | allowed_providers, 0) != 1) { |
532 | error("Refusing add key: " | 617 | error("Refusing add key: " |
533 | "provider %s not whitelisted", sk_provider); | 618 | "provider %s not allowed", sk_provider); |
534 | free(sk_provider); | 619 | free(sk_provider); |
535 | goto send; | 620 | goto send; |
536 | } | 621 | } |
@@ -685,9 +770,9 @@ process_add_smartcard_key(SocketEntry *e) | |||
685 | provider, strerror(errno)); | 770 | provider, strerror(errno)); |
686 | goto send; | 771 | goto send; |
687 | } | 772 | } |
688 | if (match_pattern_list(canonical_provider, provider_whitelist, 0) != 1) { | 773 | if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) { |
689 | verbose("refusing PKCS#11 add of \"%.100s\": " | 774 | verbose("refusing PKCS#11 add of \"%.100s\": " |
690 | "provider not whitelisted", canonical_provider); | 775 | "provider not allowed", canonical_provider); |
691 | goto send; | 776 | goto send; |
692 | } | 777 | } |
693 | debug("%s: add %.100s", __func__, canonical_provider); | 778 | debug("%s: add %.100s", __func__, canonical_provider); |
@@ -767,8 +852,10 @@ send: | |||
767 | } | 852 | } |
768 | #endif /* ENABLE_PKCS11 */ | 853 | #endif /* ENABLE_PKCS11 */ |
769 | 854 | ||
770 | /* dispatch incoming messages */ | 855 | /* |
771 | 856 | * dispatch incoming message. | |
857 | * returns 1 on success, 0 for incomplete messages or -1 on error. | ||
858 | */ | ||
772 | static int | 859 | static int |
773 | process_message(u_int socknum) | 860 | process_message(u_int socknum) |
774 | { | 861 | { |
@@ -822,7 +909,7 @@ process_message(u_int socknum) | |||
822 | /* send a fail message for all other request types */ | 909 | /* send a fail message for all other request types */ |
823 | send_status(e, 0); | 910 | send_status(e, 0); |
824 | } | 911 | } |
825 | return 0; | 912 | return 1; |
826 | } | 913 | } |
827 | 914 | ||
828 | switch (type) { | 915 | switch (type) { |
@@ -866,7 +953,7 @@ process_message(u_int socknum) | |||
866 | send_status(e, 0); | 953 | send_status(e, 0); |
867 | break; | 954 | break; |
868 | } | 955 | } |
869 | return 0; | 956 | return 1; |
870 | } | 957 | } |
871 | 958 | ||
872 | static void | 959 | static void |
@@ -957,7 +1044,12 @@ handle_conn_read(u_int socknum) | |||
957 | if ((r = sshbuf_put(sockets[socknum].input, buf, len)) != 0) | 1044 | if ((r = sshbuf_put(sockets[socknum].input, buf, len)) != 0) |
958 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); | 1045 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
959 | explicit_bzero(buf, sizeof(buf)); | 1046 | explicit_bzero(buf, sizeof(buf)); |
960 | process_message(socknum); | 1047 | for (;;) { |
1048 | if ((r = process_message(socknum)) == -1) | ||
1049 | return -1; | ||
1050 | else if (r == 0) | ||
1051 | break; | ||
1052 | } | ||
961 | return 0; | 1053 | return 0; |
962 | } | 1054 | } |
963 | 1055 | ||
@@ -1170,7 +1262,9 @@ usage(void) | |||
1170 | { | 1262 | { |
1171 | fprintf(stderr, | 1263 | fprintf(stderr, |
1172 | "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" | 1264 | "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
1173 | " [-P provider_whitelist] [-t life] [command [arg ...]]\n" | 1265 | " [-P allowed_providers] [-t life]\n" |
1266 | " ssh-agent [-a bind_address] [-E fingerprint_hash] [-P allowed_providers]\n" | ||
1267 | " [-t life] command [arg ...]\n" | ||
1174 | " ssh-agent [-c | -s] -k\n"); | 1268 | " ssh-agent [-c | -s] -k\n"); |
1175 | exit(1); | 1269 | exit(1); |
1176 | } | 1270 | } |
@@ -1212,7 +1306,7 @@ main(int ac, char **av) | |||
1212 | __progname = ssh_get_progname(av[0]); | 1306 | __progname = ssh_get_progname(av[0]); |
1213 | seed_rng(); | 1307 | seed_rng(); |
1214 | 1308 | ||
1215 | while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) { | 1309 | while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) { |
1216 | switch (ch) { | 1310 | switch (ch) { |
1217 | case 'E': | 1311 | case 'E': |
1218 | fingerprint_hash = ssh_digest_alg_by_name(optarg); | 1312 | fingerprint_hash = ssh_digest_alg_by_name(optarg); |
@@ -1227,10 +1321,16 @@ main(int ac, char **av) | |||
1227 | case 'k': | 1321 | case 'k': |
1228 | k_flag++; | 1322 | k_flag++; |
1229 | break; | 1323 | break; |
1324 | case 'O': | ||
1325 | if (strcmp(optarg, "no-restrict-websafe") == 0) | ||
1326 | restrict_websafe = 0; | ||
1327 | else | ||
1328 | fatal("Unknown -O option"); | ||
1329 | break; | ||
1230 | case 'P': | 1330 | case 'P': |
1231 | if (provider_whitelist != NULL) | 1331 | if (allowed_providers != NULL) |
1232 | fatal("-P option already specified"); | 1332 | fatal("-P option already specified"); |
1233 | provider_whitelist = xstrdup(optarg); | 1333 | allowed_providers = xstrdup(optarg); |
1234 | break; | 1334 | break; |
1235 | case 's': | 1335 | case 's': |
1236 | if (c_flag) | 1336 | if (c_flag) |
@@ -1266,8 +1366,8 @@ main(int ac, char **av) | |||
1266 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) | 1366 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
1267 | usage(); | 1367 | usage(); |
1268 | 1368 | ||
1269 | if (provider_whitelist == NULL) | 1369 | if (allowed_providers == NULL) |
1270 | provider_whitelist = xstrdup(DEFAULT_PROVIDER_WHITELIST); | 1370 | allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS); |
1271 | 1371 | ||
1272 | if (ac == 0 && !c_flag && !s_flag) { | 1372 | if (ac == 0 && !c_flag && !s_flag) { |
1273 | shell = getenv("SHELL"); | 1373 | shell = getenv("SHELL"); |