diff options
author | djm@openbsd.org <djm@openbsd.org> | 2020-05-26 01:26:58 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2020-05-27 10:14:45 +1000 |
commit | 0c111eb84efba7c2a38b2cc3278901a0123161b9 (patch) | |
tree | d76647bcf949c959a4f8a2019b079961db8f1c8a /ssh-agent.c | |
parent | 9c5f64b6cb3a68b99915202d318b842c6c76cf14 (diff) |
upstream: Restrict ssh-agent from signing web challenges for FIDO
keys.
When signing messages in ssh-agent using a FIDO key that has an
application string that does not start with "ssh:", ensure that the
message being signed is one of the forms expected for the SSH protocol
(currently pubkey authentication and sshsig signatures).
This prevents ssh-agent forwarding on a host that has FIDO keys
attached granting the ability for the remote side to sign challenges
for web authentication using those keys too.
Note that the converse case of web browsers signing SSH challenges is
already precluded because no web RP can have the "ssh:" prefix in the
application string that we require.
ok markus@
OpenBSD-Commit-ID: 9ab6012574ed0352d2f097d307f4a988222d1b19
Diffstat (limited to 'ssh-agent.c')
-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"); |