diff options
Diffstat (limited to 'sshconnect2.c')
-rw-r--r-- | sshconnect2.c | 172 |
1 files changed, 124 insertions, 48 deletions
diff --git a/sshconnect2.c b/sshconnect2.c index 87fa70a40..af00fb30c 100644 --- a/sshconnect2.c +++ b/sshconnect2.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sshconnect2.c,v 1.308 2019/08/05 11:50:33 dtucker Exp $ */ | 1 | /* $OpenBSD: sshconnect2.c,v 1.320 2020/02/06 22:48:23 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * Copyright (c) 2008 Damien Miller. All rights reserved. | 4 | * Copyright (c) 2008 Damien Miller. All rights reserved. |
@@ -36,9 +36,9 @@ | |||
36 | #include <netdb.h> | 36 | #include <netdb.h> |
37 | #include <pwd.h> | 37 | #include <pwd.h> |
38 | #include <signal.h> | 38 | #include <signal.h> |
39 | #include <stdarg.h> | ||
40 | #include <stdio.h> | 39 | #include <stdio.h> |
41 | #include <string.h> | 40 | #include <string.h> |
41 | #include <stdarg.h> | ||
42 | #include <unistd.h> | 42 | #include <unistd.h> |
43 | #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) | 43 | #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) |
44 | #include <vis.h> | 44 | #include <vis.h> |
@@ -72,6 +72,8 @@ | |||
72 | #include "hostfile.h" | 72 | #include "hostfile.h" |
73 | #include "ssherr.h" | 73 | #include "ssherr.h" |
74 | #include "utf8.h" | 74 | #include "utf8.h" |
75 | #include "ssh-sk.h" | ||
76 | #include "sk-api.h" | ||
75 | 77 | ||
76 | #ifdef GSSAPI | 78 | #ifdef GSSAPI |
77 | #include "ssh-gss.h" | 79 | #include "ssh-gss.h" |
@@ -117,7 +119,7 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port) | |||
117 | for (i = 0; i < options.num_system_hostfiles; i++) | 119 | for (i = 0; i < options.num_system_hostfiles; i++) |
118 | load_hostkeys(hostkeys, hostname, options.system_hostfiles[i]); | 120 | load_hostkeys(hostkeys, hostname, options.system_hostfiles[i]); |
119 | 121 | ||
120 | oavail = avail = xstrdup(KEX_DEFAULT_PK_ALG); | 122 | oavail = avail = xstrdup(options.hostkeyalgorithms); |
121 | maxlen = strlen(avail) + 1; | 123 | maxlen = strlen(avail) + 1; |
122 | first = xmalloc(maxlen); | 124 | first = xmalloc(maxlen); |
123 | last = xmalloc(maxlen); | 125 | last = xmalloc(maxlen); |
@@ -159,11 +161,28 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) | |||
159 | { | 161 | { |
160 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; | 162 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; |
161 | char *s, *all_key; | 163 | char *s, *all_key; |
162 | int r; | 164 | int r, use_known_hosts_order = 0; |
163 | 165 | ||
164 | xxx_host = host; | 166 | xxx_host = host; |
165 | xxx_hostaddr = hostaddr; | 167 | xxx_hostaddr = hostaddr; |
166 | 168 | ||
169 | /* | ||
170 | * If the user has not specified HostkeyAlgorithms, or has only | ||
171 | * appended or removed algorithms from that list then prefer algorithms | ||
172 | * that are in the list that are supported by known_hosts keys. | ||
173 | */ | ||
174 | if (options.hostkeyalgorithms == NULL || | ||
175 | options.hostkeyalgorithms[0] == '-' || | ||
176 | options.hostkeyalgorithms[0] == '+') | ||
177 | use_known_hosts_order = 1; | ||
178 | |||
179 | /* Expand or fill in HostkeyAlgorithms */ | ||
180 | all_key = sshkey_alg_list(0, 0, 1, ','); | ||
181 | if (kex_assemble_names(&options.hostkeyalgorithms, | ||
182 | kex_default_pk_alg(), all_key) != 0) | ||
183 | fatal("%s: kex_assemble_namelist", __func__); | ||
184 | free(all_key); | ||
185 | |||
167 | if ((s = kex_names_cat(options.kex_algorithms, "ext-info-c")) == NULL) | 186 | if ((s = kex_names_cat(options.kex_algorithms, "ext-info-c")) == NULL) |
168 | fatal("%s: kex_names_cat", __func__); | 187 | fatal("%s: kex_names_cat", __func__); |
169 | myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s); | 188 | myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s); |
@@ -172,25 +191,19 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) | |||
172 | myproposal[PROPOSAL_ENC_ALGS_STOC] = | 191 | myproposal[PROPOSAL_ENC_ALGS_STOC] = |
173 | compat_cipher_proposal(options.ciphers); | 192 | compat_cipher_proposal(options.ciphers); |
174 | myproposal[PROPOSAL_COMP_ALGS_CTOS] = | 193 | myproposal[PROPOSAL_COMP_ALGS_CTOS] = |
175 | myproposal[PROPOSAL_COMP_ALGS_STOC] = options.compression ? | 194 | myproposal[PROPOSAL_COMP_ALGS_STOC] = |
176 | "zlib@openssh.com,zlib,none" : "none,zlib@openssh.com,zlib"; | 195 | (char *)compression_alg_list(options.compression); |
177 | myproposal[PROPOSAL_MAC_ALGS_CTOS] = | 196 | myproposal[PROPOSAL_MAC_ALGS_CTOS] = |
178 | myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; | 197 | myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; |
179 | if (options.hostkeyalgorithms != NULL) { | 198 | if (use_known_hosts_order) { |
180 | all_key = sshkey_alg_list(0, 0, 1, ','); | 199 | /* Query known_hosts and prefer algorithms that appear there */ |
181 | if (kex_assemble_names(&options.hostkeyalgorithms, | ||
182 | KEX_DEFAULT_PK_ALG, all_key) != 0) | ||
183 | fatal("%s: kex_assemble_namelist", __func__); | ||
184 | free(all_key); | ||
185 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = | ||
186 | compat_pkalg_proposal(options.hostkeyalgorithms); | ||
187 | } else { | ||
188 | /* Enforce default */ | ||
189 | options.hostkeyalgorithms = xstrdup(KEX_DEFAULT_PK_ALG); | ||
190 | /* Prefer algorithms that we already have keys for */ | ||
191 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = | 200 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = |
192 | compat_pkalg_proposal( | 201 | compat_pkalg_proposal( |
193 | order_hostkeyalgs(host, hostaddr, port)); | 202 | order_hostkeyalgs(host, hostaddr, port)); |
203 | } else { | ||
204 | /* Use specified HostkeyAlgorithms exactly */ | ||
205 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = | ||
206 | compat_pkalg_proposal(options.hostkeyalgorithms); | ||
194 | } | 207 | } |
195 | 208 | ||
196 | if (options.rekey_limit || options.rekey_interval) | 209 | if (options.rekey_limit || options.rekey_interval) |
@@ -601,17 +614,23 @@ static char * | |||
601 | format_identity(Identity *id) | 614 | format_identity(Identity *id) |
602 | { | 615 | { |
603 | char *fp = NULL, *ret = NULL; | 616 | char *fp = NULL, *ret = NULL; |
617 | const char *note = ""; | ||
604 | 618 | ||
605 | if (id->key != NULL) { | 619 | if (id->key != NULL) { |
606 | fp = sshkey_fingerprint(id->key, options.fingerprint_hash, | 620 | fp = sshkey_fingerprint(id->key, options.fingerprint_hash, |
607 | SSH_FP_DEFAULT); | 621 | SSH_FP_DEFAULT); |
608 | } | 622 | } |
623 | if (id->key) { | ||
624 | if ((id->key->flags & SSHKEY_FLAG_EXT) != 0) | ||
625 | note = " token"; | ||
626 | else if (sshkey_is_sk(id->key)) | ||
627 | note = " authenticator"; | ||
628 | } | ||
609 | xasprintf(&ret, "%s %s%s%s%s%s%s", | 629 | xasprintf(&ret, "%s %s%s%s%s%s%s", |
610 | id->filename, | 630 | id->filename, |
611 | id->key ? sshkey_type(id->key) : "", id->key ? " " : "", | 631 | id->key ? sshkey_type(id->key) : "", id->key ? " " : "", |
612 | fp ? fp : "", | 632 | fp ? fp : "", |
613 | id->userprovided ? " explicit" : "", | 633 | id->userprovided ? " explicit" : "", note, |
614 | (id->key && (id->key->flags & SSHKEY_FLAG_EXT)) ? " token" : "", | ||
615 | id->agent_fd != -1 ? " agent" : ""); | 634 | id->agent_fd != -1 ? " agent" : ""); |
616 | free(fp); | 635 | free(fp); |
617 | return ret; | 636 | return ret; |
@@ -1140,8 +1159,13 @@ static int | |||
1140 | identity_sign(struct identity *id, u_char **sigp, size_t *lenp, | 1159 | identity_sign(struct identity *id, u_char **sigp, size_t *lenp, |
1141 | const u_char *data, size_t datalen, u_int compat, const char *alg) | 1160 | const u_char *data, size_t datalen, u_int compat, const char *alg) |
1142 | { | 1161 | { |
1143 | struct sshkey *prv; | 1162 | struct sshkey *sign_key = NULL, *prv = NULL; |
1144 | int r; | 1163 | int r = SSH_ERR_INTERNAL_ERROR; |
1164 | struct notifier_ctx *notifier = NULL; | ||
1165 | char *fp = NULL; | ||
1166 | |||
1167 | *sigp = NULL; | ||
1168 | *lenp = 0; | ||
1145 | 1169 | ||
1146 | /* The agent supports this key. */ | 1170 | /* The agent supports this key. */ |
1147 | if (id->key != NULL && id->agent_fd != -1) { | 1171 | if (id->key != NULL && id->agent_fd != -1) { |
@@ -1155,27 +1179,47 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp, | |||
1155 | */ | 1179 | */ |
1156 | if (id->key != NULL && | 1180 | if (id->key != NULL && |
1157 | (id->isprivate || (id->key->flags & SSHKEY_FLAG_EXT))) { | 1181 | (id->isprivate || (id->key->flags & SSHKEY_FLAG_EXT))) { |
1158 | if ((r = sshkey_sign(id->key, sigp, lenp, data, datalen, | 1182 | sign_key = id->key; |
1159 | alg, compat)) != 0) | 1183 | } else { |
1160 | return r; | 1184 | /* Load the private key from the file. */ |
1161 | /* | 1185 | if ((prv = load_identity_file(id)) == NULL) |
1162 | * PKCS#11 tokens may not support all signature algorithms, | 1186 | return SSH_ERR_KEY_NOT_FOUND; |
1163 | * so check what we get back. | 1187 | if (id->key != NULL && !sshkey_equal_public(prv, id->key)) { |
1164 | */ | 1188 | error("%s: private key %s contents do not match public", |
1165 | if ((r = sshkey_check_sigtype(*sigp, *lenp, alg)) != 0) | 1189 | __func__, id->filename); |
1166 | return r; | 1190 | r = SSH_ERR_KEY_NOT_FOUND; |
1167 | return 0; | 1191 | goto out; |
1192 | } | ||
1193 | sign_key = prv; | ||
1194 | if (sshkey_is_sk(sign_key) && | ||
1195 | (sign_key->sk_flags & SSH_SK_USER_PRESENCE_REQD)) { | ||
1196 | /* XXX match batch mode should just skip these keys? */ | ||
1197 | if ((fp = sshkey_fingerprint(sign_key, | ||
1198 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) | ||
1199 | fatal("%s: sshkey_fingerprint", __func__); | ||
1200 | notifier = notify_start(options.batch_mode, | ||
1201 | "Confirm user presence for key %s %s", | ||
1202 | sshkey_type(sign_key), fp); | ||
1203 | free(fp); | ||
1204 | } | ||
1168 | } | 1205 | } |
1169 | 1206 | if ((r = sshkey_sign(sign_key, sigp, lenp, data, datalen, | |
1170 | /* Load the private key from the file. */ | 1207 | alg, options.sk_provider, compat)) != 0) { |
1171 | if ((prv = load_identity_file(id)) == NULL) | 1208 | debug("%s: sshkey_sign: %s", __func__, ssh_err(r)); |
1172 | return SSH_ERR_KEY_NOT_FOUND; | 1209 | goto out; |
1173 | if (id->key != NULL && !sshkey_equal_public(prv, id->key)) { | ||
1174 | error("%s: private key %s contents do not match public", | ||
1175 | __func__, id->filename); | ||
1176 | return SSH_ERR_KEY_NOT_FOUND; | ||
1177 | } | 1210 | } |
1178 | r = sshkey_sign(prv, sigp, lenp, data, datalen, alg, compat); | 1211 | /* |
1212 | * PKCS#11 tokens may not support all signature algorithms, | ||
1213 | * so check what we get back. | ||
1214 | */ | ||
1215 | if ((r = sshkey_check_sigtype(*sigp, *lenp, alg)) != 0) { | ||
1216 | debug("%s: sshkey_check_sigtype: %s", __func__, ssh_err(r)); | ||
1217 | goto out; | ||
1218 | } | ||
1219 | /* success */ | ||
1220 | r = 0; | ||
1221 | out: | ||
1222 | notify_complete(notifier); | ||
1179 | sshkey_free(prv); | 1223 | sshkey_free(prv); |
1180 | return r; | 1224 | return r; |
1181 | } | 1225 | } |
@@ -1278,7 +1322,7 @@ sign_and_send_pubkey(struct ssh *ssh, Identity *id) | |||
1278 | error("%s: no mutual signature supported", __func__); | 1322 | error("%s: no mutual signature supported", __func__); |
1279 | goto out; | 1323 | goto out; |
1280 | } | 1324 | } |
1281 | debug3("%s: signing using %s", __func__, alg); | 1325 | debug3("%s: signing using %s %s", __func__, alg, fp); |
1282 | 1326 | ||
1283 | sshbuf_free(b); | 1327 | sshbuf_free(b); |
1284 | if ((b = sshbuf_new()) == NULL) | 1328 | if ((b = sshbuf_new()) == NULL) |
@@ -1325,7 +1369,9 @@ sign_and_send_pubkey(struct ssh *ssh, Identity *id) | |||
1325 | loc, sshkey_type(id->key), fp); | 1369 | loc, sshkey_type(id->key), fp); |
1326 | continue; | 1370 | continue; |
1327 | } | 1371 | } |
1328 | error("%s: signing failed: %s", __func__, ssh_err(r)); | 1372 | error("%s: signing failed for %s \"%s\"%s: %s", __func__, |
1373 | sshkey_type(sign_id->key), sign_id->filename, | ||
1374 | id->agent_fd != -1 ? " from agent" : "", ssh_err(r)); | ||
1329 | goto out; | 1375 | goto out; |
1330 | } | 1376 | } |
1331 | if (slen == 0 || signature == NULL) /* shouldn't happen */ | 1377 | if (slen == 0 || signature == NULL) /* shouldn't happen */ |
@@ -1450,6 +1496,14 @@ load_identity_file(Identity *id) | |||
1450 | quit = 1; | 1496 | quit = 1; |
1451 | break; | 1497 | break; |
1452 | } | 1498 | } |
1499 | if (private != NULL && sshkey_is_sk(private) && | ||
1500 | options.sk_provider == NULL) { | ||
1501 | debug("key \"%s\" is an authenticator-hosted key, " | ||
1502 | "but no provider specified", id->filename); | ||
1503 | sshkey_free(private); | ||
1504 | private = NULL; | ||
1505 | quit = 1; | ||
1506 | } | ||
1453 | if (!quit && private != NULL && id->agent_fd == -1 && | 1507 | if (!quit && private != NULL && id->agent_fd == -1 && |
1454 | !(id->key && id->isprivate)) | 1508 | !(id->key && id->isprivate)) |
1455 | maybe_add_key_to_agent(id->filename, private, comment, | 1509 | maybe_add_key_to_agent(id->filename, private, comment, |
@@ -1520,8 +1574,19 @@ pubkey_prepare(Authctxt *authctxt) | |||
1520 | /* list of keys stored in the filesystem and PKCS#11 */ | 1574 | /* list of keys stored in the filesystem and PKCS#11 */ |
1521 | for (i = 0; i < options.num_identity_files; i++) { | 1575 | for (i = 0; i < options.num_identity_files; i++) { |
1522 | key = options.identity_keys[i]; | 1576 | key = options.identity_keys[i]; |
1523 | if (key && key->cert && key->cert->type != SSH2_CERT_TYPE_USER) | 1577 | if (key && key->cert && |
1578 | key->cert->type != SSH2_CERT_TYPE_USER) { | ||
1579 | debug("%s: ignoring certificate %s: not a user " | ||
1580 | "certificate", __func__, | ||
1581 | options.identity_files[i]); | ||
1582 | continue; | ||
1583 | } | ||
1584 | if (key && sshkey_is_sk(key) && options.sk_provider == NULL) { | ||
1585 | debug("%s: ignoring authenticator-hosted key %s as no " | ||
1586 | "SecurityKeyProvider has been specified", | ||
1587 | __func__, options.identity_files[i]); | ||
1524 | continue; | 1588 | continue; |
1589 | } | ||
1525 | options.identity_keys[i] = NULL; | 1590 | options.identity_keys[i] = NULL; |
1526 | id = xcalloc(1, sizeof(*id)); | 1591 | id = xcalloc(1, sizeof(*id)); |
1527 | id->agent_fd = -1; | 1592 | id->agent_fd = -1; |
@@ -1534,8 +1599,19 @@ pubkey_prepare(Authctxt *authctxt) | |||
1534 | for (i = 0; i < options.num_certificate_files; i++) { | 1599 | for (i = 0; i < options.num_certificate_files; i++) { |
1535 | key = options.certificates[i]; | 1600 | key = options.certificates[i]; |
1536 | if (!sshkey_is_cert(key) || key->cert == NULL || | 1601 | if (!sshkey_is_cert(key) || key->cert == NULL || |
1537 | key->cert->type != SSH2_CERT_TYPE_USER) | 1602 | key->cert->type != SSH2_CERT_TYPE_USER) { |
1603 | debug("%s: ignoring certificate %s: not a user " | ||
1604 | "certificate", __func__, | ||
1605 | options.identity_files[i]); | ||
1538 | continue; | 1606 | continue; |
1607 | } | ||
1608 | if (key && sshkey_is_sk(key) && options.sk_provider == NULL) { | ||
1609 | debug("%s: ignoring authenticator-hosted key " | ||
1610 | "certificate %s as no " | ||
1611 | "SecurityKeyProvider has been specified", | ||
1612 | __func__, options.identity_files[i]); | ||
1613 | continue; | ||
1614 | } | ||
1539 | id = xcalloc(1, sizeof(*id)); | 1615 | id = xcalloc(1, sizeof(*id)); |
1540 | id->agent_fd = -1; | 1616 | id->agent_fd = -1; |
1541 | id->key = key; | 1617 | id->key = key; |
@@ -1860,7 +1936,7 @@ ssh_keysign(struct ssh *ssh, struct sshkey *key, u_char **sigp, size_t *lenp, | |||
1860 | error("%s: fork: %s", __func__, strerror(errno)); | 1936 | error("%s: fork: %s", __func__, strerror(errno)); |
1861 | return -1; | 1937 | return -1; |
1862 | } | 1938 | } |
1863 | osigchld = signal(SIGCHLD, SIG_DFL); | 1939 | osigchld = ssh_signal(SIGCHLD, SIG_DFL); |
1864 | if (pid == 0) { | 1940 | if (pid == 0) { |
1865 | close(from[0]); | 1941 | close(from[0]); |
1866 | if (dup2(from[1], STDOUT_FILENO) == -1) | 1942 | if (dup2(from[1], STDOUT_FILENO) == -1) |
@@ -1932,11 +2008,11 @@ ssh_keysign(struct ssh *ssh, struct sshkey *key, u_char **sigp, size_t *lenp, | |||
1932 | if ((r = sshbuf_get_string(b, sigp, lenp)) != 0) { | 2008 | if ((r = sshbuf_get_string(b, sigp, lenp)) != 0) { |
1933 | error("%s: buffer error: %s", __func__, ssh_err(r)); | 2009 | error("%s: buffer error: %s", __func__, ssh_err(r)); |
1934 | fail: | 2010 | fail: |
1935 | signal(SIGCHLD, osigchld); | 2011 | ssh_signal(SIGCHLD, osigchld); |
1936 | sshbuf_free(b); | 2012 | sshbuf_free(b); |
1937 | return -1; | 2013 | return -1; |
1938 | } | 2014 | } |
1939 | signal(SIGCHLD, osigchld); | 2015 | ssh_signal(SIGCHLD, osigchld); |
1940 | sshbuf_free(b); | 2016 | sshbuf_free(b); |
1941 | 2017 | ||
1942 | return 0; | 2018 | return 0; |