summaryrefslogtreecommitdiff
path: root/kexgsss.c
diff options
context:
space:
mode:
authorSimon Wilkinson <simon@sxw.org.uk>2014-02-09 16:09:48 +0000
committerColin Watson <cjwatson@debian.org>2020-02-21 12:01:36 +0000
commit34aff3aa136e5a65f441b25811dd466488fda087 (patch)
treee2170faeed03d67545255d3d3c9d62280414c0b2 /kexgsss.c
parentf0de78bd4f29fa688c5df116f3f9cd43543a76d0 (diff)
GSSAPI key exchange support
This patch has been rejected upstream: "None of the OpenSSH developers are in favour of adding this, and this situation has not changed for several years. This is not a slight on Simon's patch, which is of fine quality, but just that a) we don't trust GSSAPI implementations that much and b) we don't like adding new KEX since they are pre-auth attack surface. This one is particularly scary, since it requires hooks out to typically root-owned system resources." However, quite a lot of people rely on this in Debian, and it's better to have it merged into the main openssh package rather than having separate -krb5 packages (as we used to have). It seems to have a generally good security history. Origin: other, https://github.com/openssh-gsskex/openssh-gsskex/commits/debian/master Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242 Last-Updated: 2020-02-21 Patch-Name: gssapi.patch
Diffstat (limited to 'kexgsss.c')
-rw-r--r--kexgsss.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/kexgsss.c b/kexgsss.c
new file mode 100644
index 000000000..60bc02deb
--- /dev/null
+++ b/kexgsss.c
@@ -0,0 +1,474 @@
1/*
2 * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "includes.h"
26
27#if defined(GSSAPI) && defined(WITH_OPENSSL)
28
29#include <string.h>
30
31#include <openssl/crypto.h>
32#include <openssl/bn.h>
33
34#include "xmalloc.h"
35#include "sshbuf.h"
36#include "ssh2.h"
37#include "sshkey.h"
38#include "cipher.h"
39#include "kex.h"
40#include "log.h"
41#include "packet.h"
42#include "dh.h"
43#include "ssh-gss.h"
44#include "monitor_wrap.h"
45#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */
46#include "servconf.h"
47#include "ssh-gss.h"
48#include "digest.h"
49#include "ssherr.h"
50
51extern ServerOptions options;
52
53int
54kexgss_server(struct ssh *ssh)
55{
56 struct kex *kex = ssh->kex;
57 OM_uint32 maj_status, min_status;
58
59 /*
60 * Some GSSAPI implementations use the input value of ret_flags (an
61 * output variable) as a means of triggering mechanism specific
62 * features. Initializing it to zero avoids inadvertently
63 * activating this non-standard behaviour.
64 */
65
66 OM_uint32 ret_flags = 0;
67 gss_buffer_desc gssbuf, recv_tok, msg_tok;
68 gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
69 Gssctxt *ctxt = NULL;
70 struct sshbuf *shared_secret = NULL;
71 struct sshbuf *client_pubkey = NULL;
72 struct sshbuf *server_pubkey = NULL;
73 struct sshbuf *empty = sshbuf_new();
74 int type = 0;
75 gss_OID oid;
76 char *mechs;
77 u_char hash[SSH_DIGEST_MAX_LENGTH];
78 size_t hashlen;
79 int r;
80
81 /* Initialise GSSAPI */
82
83 /* If we're rekeying, privsep means that some of the private structures
84 * in the GSSAPI code are no longer available. This kludges them back
85 * into life
86 */
87 if (!ssh_gssapi_oid_table_ok()) {
88 mechs = ssh_gssapi_server_mechanisms();
89 free(mechs);
90 }
91
92 debug2("%s: Identifying %s", __func__, kex->name);
93 oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
94 if (oid == GSS_C_NO_OID)
95 fatal("Unknown gssapi mechanism");
96
97 debug2("%s: Acquiring credentials", __func__);
98
99 if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
100 fatal("Unable to acquire credentials for the server");
101
102 do {
103 debug("Wait SSH2_MSG_KEXGSS_INIT");
104 type = ssh_packet_read(ssh);
105 switch(type) {
106 case SSH2_MSG_KEXGSS_INIT:
107 if (client_pubkey != NULL)
108 fatal("Received KEXGSS_INIT after initialising");
109 if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
110 &recv_tok)) != 0 ||
111 (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 ||
112 (r = sshpkt_get_end(ssh)) != 0)
113 fatal("sshpkt failed: %s", ssh_err(r));
114
115 switch (kex->kex_type) {
116 case KEX_GSS_GRP1_SHA1:
117 case KEX_GSS_GRP14_SHA1:
118 case KEX_GSS_GRP14_SHA256:
119 case KEX_GSS_GRP16_SHA512:
120 r = kex_dh_enc(kex, client_pubkey, &server_pubkey,
121 &shared_secret);
122 break;
123 case KEX_GSS_NISTP256_SHA256:
124 r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey,
125 &shared_secret);
126 break;
127 case KEX_GSS_C25519_SHA256:
128 r = kex_c25519_enc(kex, client_pubkey, &server_pubkey,
129 &shared_secret);
130 break;
131 default:
132 fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type);
133 }
134 if (r != 0)
135 goto out;
136
137 /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
138 break;
139 case SSH2_MSG_KEXGSS_CONTINUE:
140 if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
141 &recv_tok)) != 0 ||
142 (r = sshpkt_get_end(ssh)) != 0)
143 fatal("sshpkt failed: %s", ssh_err(r));
144 break;
145 default:
146 sshpkt_disconnect(ssh,
147 "Protocol error: didn't expect packet type %d",
148 type);
149 }
150
151 maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
152 &send_tok, &ret_flags));
153
154 gss_release_buffer(&min_status, &recv_tok);
155
156 if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
157 fatal("Zero length token output when incomplete");
158
159 if (client_pubkey == NULL)
160 fatal("No client public key");
161
162 if (maj_status & GSS_S_CONTINUE_NEEDED) {
163 debug("Sending GSSAPI_CONTINUE");
164 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
165 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
166 (r = sshpkt_send(ssh)) != 0)
167 fatal("sshpkt failed: %s", ssh_err(r));
168 gss_release_buffer(&min_status, &send_tok);
169 }
170 } while (maj_status & GSS_S_CONTINUE_NEEDED);
171
172 if (GSS_ERROR(maj_status)) {
173 if (send_tok.length > 0) {
174 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
175 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
176 (r = sshpkt_send(ssh)) != 0)
177 fatal("sshpkt failed: %s", ssh_err(r));
178 }
179 fatal("accept_ctx died");
180 }
181
182 if (!(ret_flags & GSS_C_MUTUAL_FLAG))
183 fatal("Mutual Authentication flag wasn't set");
184
185 if (!(ret_flags & GSS_C_INTEG_FLAG))
186 fatal("Integrity flag wasn't set");
187
188 hashlen = sizeof(hash);
189 if ((r = kex_gen_hash(
190 kex->hash_alg,
191 kex->client_version,
192 kex->server_version,
193 kex->peer,
194 kex->my,
195 empty,
196 client_pubkey,
197 server_pubkey,
198 shared_secret,
199 hash, &hashlen)) != 0)
200 goto out;
201
202 gssbuf.value = hash;
203 gssbuf.length = hashlen;
204
205 if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
206 fatal("Couldn't get MIC");
207
208 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
209 (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 ||
210 (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
211 fatal("sshpkt failed: %s", ssh_err(r));
212
213 if (send_tok.length != 0) {
214 if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
215 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
216 fatal("sshpkt failed: %s", ssh_err(r));
217 } else {
218 if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
219 fatal("sshpkt failed: %s", ssh_err(r));
220 }
221 if ((r = sshpkt_send(ssh)) != 0)
222 fatal("sshpkt_send failed: %s", ssh_err(r));
223
224 gss_release_buffer(&min_status, &send_tok);
225 gss_release_buffer(&min_status, &msg_tok);
226
227 if (gss_kex_context == NULL)
228 gss_kex_context = ctxt;
229 else
230 ssh_gssapi_delete_ctx(&ctxt);
231
232 if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
233 r = kex_send_newkeys(ssh);
234
235 /* If this was a rekey, then save out any delegated credentials we
236 * just exchanged. */
237 if (options.gss_store_rekey)
238 ssh_gssapi_rekey_creds();
239out:
240 sshbuf_free(empty);
241 explicit_bzero(hash, sizeof(hash));
242 sshbuf_free(shared_secret);
243 sshbuf_free(client_pubkey);
244 sshbuf_free(server_pubkey);
245 return r;
246}
247
248int
249kexgssgex_server(struct ssh *ssh)
250{
251 struct kex *kex = ssh->kex;
252 OM_uint32 maj_status, min_status;
253
254 /*
255 * Some GSSAPI implementations use the input value of ret_flags (an
256 * output variable) as a means of triggering mechanism specific
257 * features. Initializing it to zero avoids inadvertently
258 * activating this non-standard behaviour.
259 */
260
261 OM_uint32 ret_flags = 0;
262 gss_buffer_desc gssbuf, recv_tok, msg_tok;
263 gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
264 Gssctxt *ctxt = NULL;
265 struct sshbuf *shared_secret = NULL;
266 int type = 0;
267 gss_OID oid;
268 char *mechs;
269 u_char hash[SSH_DIGEST_MAX_LENGTH];
270 size_t hashlen;
271 BIGNUM *dh_client_pub = NULL;
272 const BIGNUM *pub_key, *dh_p, *dh_g;
273 int min = -1, max = -1, nbits = -1;
274 int cmin = -1, cmax = -1; /* client proposal */
275 struct sshbuf *empty = sshbuf_new();
276 int r;
277
278 /* Initialise GSSAPI */
279
280 /* If we're rekeying, privsep means that some of the private structures
281 * in the GSSAPI code are no longer available. This kludges them back
282 * into life
283 */
284 if (!ssh_gssapi_oid_table_ok())
285 if ((mechs = ssh_gssapi_server_mechanisms()))
286 free(mechs);
287
288 debug2("%s: Identifying %s", __func__, kex->name);
289 oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
290 if (oid == GSS_C_NO_OID)
291 fatal("Unknown gssapi mechanism");
292
293 debug2("%s: Acquiring credentials", __func__);
294
295 if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid))))
296 fatal("Unable to acquire credentials for the server");
297
298 /* 5. S generates an ephemeral key pair (do the allocations early) */
299 debug("Doing group exchange");
300 ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ);
301 /* store client proposal to provide valid signature */
302 if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 ||
303 (r = sshpkt_get_u32(ssh, &nbits)) != 0 ||
304 (r = sshpkt_get_u32(ssh, &cmax)) != 0 ||
305 (r = sshpkt_get_end(ssh)) != 0)
306 fatal("sshpkt failed: %s", ssh_err(r));
307 kex->nbits = nbits;
308 kex->min = cmin;
309 kex->max = cmax;
310 min = MAX(DH_GRP_MIN, cmin);
311 max = MIN(DH_GRP_MAX, cmax);
312 nbits = MAXIMUM(DH_GRP_MIN, nbits);
313 nbits = MINIMUM(DH_GRP_MAX, nbits);
314 if (max < min || nbits < min || max < nbits)
315 fatal("GSS_GEX, bad parameters: %d !< %d !< %d",
316 min, nbits, max);
317 kex->dh = PRIVSEP(choose_dh(min, nbits, max));
318 if (kex->dh == NULL) {
319 sshpkt_disconnect(ssh, "Protocol error: no matching group found");
320 fatal("Protocol error: no matching group found");
321 }
322
323 DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
324 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 ||
325 (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
326 (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
327 (r = sshpkt_send(ssh)) != 0)
328 fatal("sshpkt failed: %s", ssh_err(r));
329
330 if ((r = ssh_packet_write_wait(ssh)) != 0)
331 fatal("ssh_packet_write_wait: %s", ssh_err(r));
332
333 /* Compute our exchange value in parallel with the client */
334 if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
335 goto out;
336
337 do {
338 debug("Wait SSH2_MSG_GSSAPI_INIT");
339 type = ssh_packet_read(ssh);
340 switch(type) {
341 case SSH2_MSG_KEXGSS_INIT:
342 if (dh_client_pub != NULL)
343 fatal("Received KEXGSS_INIT after initialising");
344 if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
345 &recv_tok)) != 0 ||
346 (r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 ||
347 (r = sshpkt_get_end(ssh)) != 0)
348 fatal("sshpkt failed: %s", ssh_err(r));
349
350 /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
351 break;
352 case SSH2_MSG_KEXGSS_CONTINUE:
353 if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
354 &recv_tok)) != 0 ||
355 (r = sshpkt_get_end(ssh)) != 0)
356 fatal("sshpkt failed: %s", ssh_err(r));
357 break;
358 default:
359 sshpkt_disconnect(ssh,
360 "Protocol error: didn't expect packet type %d",
361 type);
362 }
363
364 maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok,
365 &send_tok, &ret_flags));
366
367 gss_release_buffer(&min_status, &recv_tok);
368
369 if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
370 fatal("Zero length token output when incomplete");
371
372 if (dh_client_pub == NULL)
373 fatal("No client public key");
374
375 if (maj_status & GSS_S_CONTINUE_NEEDED) {
376 debug("Sending GSSAPI_CONTINUE");
377 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
378 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
379 (r = sshpkt_send(ssh)) != 0)
380 fatal("sshpkt failed: %s", ssh_err(r));
381 gss_release_buffer(&min_status, &send_tok);
382 }
383 } while (maj_status & GSS_S_CONTINUE_NEEDED);
384
385 if (GSS_ERROR(maj_status)) {
386 if (send_tok.length > 0) {
387 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
388 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
389 (r = sshpkt_send(ssh)) != 0)
390 fatal("sshpkt failed: %s", ssh_err(r));
391 }
392 fatal("accept_ctx died");
393 }
394
395 if (!(ret_flags & GSS_C_MUTUAL_FLAG))
396 fatal("Mutual Authentication flag wasn't set");
397
398 if (!(ret_flags & GSS_C_INTEG_FLAG))
399 fatal("Integrity flag wasn't set");
400
401 /* calculate shared secret */
402 if ((shared_secret = sshbuf_new()) == NULL) {
403 r = SSH_ERR_ALLOC_FAIL;
404 goto out;
405 }
406 if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0)
407 goto out;
408
409 DH_get0_key(kex->dh, &pub_key, NULL);
410 DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
411 hashlen = sizeof(hash);
412 if ((r = kexgex_hash(
413 kex->hash_alg,
414 kex->client_version,
415 kex->server_version,
416 kex->peer,
417 kex->my,
418 empty,
419 cmin, nbits, cmax,
420 dh_p, dh_g,
421 dh_client_pub,
422 pub_key,
423 sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
424 hash, &hashlen)) != 0)
425 fatal("kexgex_hash failed: %s", ssh_err(r));
426
427 gssbuf.value = hash;
428 gssbuf.length = hashlen;
429
430 if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok))))
431 fatal("Couldn't get MIC");
432
433 if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
434 (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 ||
435 (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
436 fatal("sshpkt failed: %s", ssh_err(r));
437
438 if (send_tok.length != 0) {
439 if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
440 (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
441 fatal("sshpkt failed: %s", ssh_err(r));
442 } else {
443 if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
444 fatal("sshpkt failed: %s", ssh_err(r));
445 }
446 if ((r = sshpkt_send(ssh)) != 0)
447 fatal("sshpkt failed: %s", ssh_err(r));
448
449 gss_release_buffer(&min_status, &send_tok);
450 gss_release_buffer(&min_status, &msg_tok);
451
452 if (gss_kex_context == NULL)
453 gss_kex_context = ctxt;
454 else
455 ssh_gssapi_delete_ctx(&ctxt);
456
457 /* Finally derive the keys and send them */
458 if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
459 r = kex_send_newkeys(ssh);
460
461 /* If this was a rekey, then save out any delegated credentials we
462 * just exchanged. */
463 if (options.gss_store_rekey)
464 ssh_gssapi_rekey_creds();
465out:
466 sshbuf_free(empty);
467 explicit_bzero(hash, sizeof(hash));
468 DH_free(kex->dh);
469 kex->dh = NULL;
470 BN_clear_free(dh_client_pub);
471 sshbuf_free(shared_secret);
472 return r;
473}
474#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */