summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2020-05-26 01:26:58 +0000
committerDamien Miller <djm@mindrot.org>2020-05-27 10:14:45 +1000
commit0c111eb84efba7c2a38b2cc3278901a0123161b9 (patch)
treed76647bcf949c959a4f8a2019b079961db8f1c8a
parent9c5f64b6cb3a68b99915202d318b842c6c76cf14 (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
-rw-r--r--ssh-agent.c110
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
168static int fingerprint_hash = SSH_FP_HASH_DEFAULT; 169static int fingerprint_hash = SSH_FP_HASH_DEFAULT;
169 170
171/* Refuse signing of non-SSH messages for web-origin FIDO keys */
172static int restrict_websafe = 1;
173
170static void 174static void
171close_socket(SocketEntry *e) 175close_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 */
296static int
297check_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 */
286static void 364static void
287process_sign_request2(SocketEntry *e) 365process_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");