diff options
-rw-r--r-- | ssh-agent.c | 110 |
1 files changed, 100 insertions, 10 deletions
diff --git a/ssh-agent.c b/ssh-agent.c index e081413b8..effffffed 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.258 2020/05/26 01:26:58 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" |
@@ -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,14 +392,20 @@ 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 | } |
326 | if ((r = sshkey_sign(id->key, &signature, &slen, | 410 | if ((r = sshkey_sign(id->key, &signature, &slen, |
327 | data, dlen, agent_decode_alg(key, flags), | 411 | data, dlen, agent_decode_alg(key, flags), |
@@ -1212,7 +1296,7 @@ main(int ac, char **av) | |||
1212 | __progname = ssh_get_progname(av[0]); | 1296 | __progname = ssh_get_progname(av[0]); |
1213 | seed_rng(); | 1297 | seed_rng(); |
1214 | 1298 | ||
1215 | while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) { | 1299 | while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) { |
1216 | switch (ch) { | 1300 | switch (ch) { |
1217 | case 'E': | 1301 | case 'E': |
1218 | fingerprint_hash = ssh_digest_alg_by_name(optarg); | 1302 | fingerprint_hash = ssh_digest_alg_by_name(optarg); |
@@ -1227,6 +1311,12 @@ main(int ac, char **av) | |||
1227 | case 'k': | 1311 | case 'k': |
1228 | k_flag++; | 1312 | k_flag++; |
1229 | break; | 1313 | break; |
1314 | case 'O': | ||
1315 | if (strcmp(optarg, "no-restrict-websafe") == 0) | ||
1316 | restrict_websafe = 0; | ||
1317 | else | ||
1318 | fatal("Unknown -O option"); | ||
1319 | break; | ||
1230 | case 'P': | 1320 | case 'P': |
1231 | if (provider_whitelist != NULL) | 1321 | if (provider_whitelist != NULL) |
1232 | fatal("-P option already specified"); | 1322 | fatal("-P option already specified"); |