diff options
author | Simon Wilkinson <simon@sxw.org.uk> | 2014-02-09 16:09:48 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2019-06-05 07:06:44 +0100 |
commit | 7ce79be85036c4b36937f1b1ba85f6094068412c (patch) | |
tree | c964917d8395ef5605cff9513aad4458b222beae /gss-genr.c | |
parent | 102062f825fb26a74295a1c089c00c4c4c76b68a (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: 2019-06-05
Patch-Name: gssapi.patch
Diffstat (limited to 'gss-genr.c')
-rw-r--r-- | gss-genr.c | 300 |
1 files changed, 296 insertions, 4 deletions
diff --git a/gss-genr.c b/gss-genr.c index d56257b4a..763a63ffa 100644 --- a/gss-genr.c +++ b/gss-genr.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* $OpenBSD: gss-genr.c,v 1.26 2018/07/10 09:13:30 djm Exp $ */ | 1 | /* $OpenBSD: gss-genr.c,v 1.26 2018/07/10 09:13:30 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. | 4 | * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. |
5 | * | 5 | * |
6 | * Redistribution and use in source and binary forms, with or without | 6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions | 7 | * modification, are permitted provided that the following conditions |
@@ -41,12 +41,36 @@ | |||
41 | #include "sshbuf.h" | 41 | #include "sshbuf.h" |
42 | #include "log.h" | 42 | #include "log.h" |
43 | #include "ssh2.h" | 43 | #include "ssh2.h" |
44 | #include "cipher.h" | ||
45 | #include "sshkey.h" | ||
46 | #include "kex.h" | ||
47 | #include "digest.h" | ||
48 | #include "packet.h" | ||
44 | 49 | ||
45 | #include "ssh-gss.h" | 50 | #include "ssh-gss.h" |
46 | 51 | ||
47 | extern u_char *session_id2; | 52 | extern u_char *session_id2; |
48 | extern u_int session_id2_len; | 53 | extern u_int session_id2_len; |
49 | 54 | ||
55 | typedef struct { | ||
56 | char *encoded; | ||
57 | gss_OID oid; | ||
58 | } ssh_gss_kex_mapping; | ||
59 | |||
60 | /* | ||
61 | * XXX - It would be nice to find a more elegant way of handling the | ||
62 | * XXX passing of the key exchange context to the userauth routines | ||
63 | */ | ||
64 | |||
65 | Gssctxt *gss_kex_context = NULL; | ||
66 | |||
67 | static ssh_gss_kex_mapping *gss_enc2oid = NULL; | ||
68 | |||
69 | int | ||
70 | ssh_gssapi_oid_table_ok(void) { | ||
71 | return (gss_enc2oid != NULL); | ||
72 | } | ||
73 | |||
50 | /* sshbuf_get for gss_buffer_desc */ | 74 | /* sshbuf_get for gss_buffer_desc */ |
51 | int | 75 | int |
52 | ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) | 76 | ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) |
@@ -62,6 +86,162 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) | |||
62 | return 0; | 86 | return 0; |
63 | } | 87 | } |
64 | 88 | ||
89 | /* sshpkt_get of gss_buffer_desc */ | ||
90 | int | ||
91 | ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g) | ||
92 | { | ||
93 | int r; | ||
94 | u_char *p; | ||
95 | size_t len; | ||
96 | |||
97 | if ((r = sshpkt_get_string(ssh, &p, &len)) != 0) | ||
98 | return r; | ||
99 | g->value = p; | ||
100 | g->length = len; | ||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Return a list of the gss-group1-sha1 mechanisms supported by this program | ||
106 | * | ||
107 | * We test mechanisms to ensure that we can use them, to avoid starting | ||
108 | * a key exchange with a bad mechanism | ||
109 | */ | ||
110 | |||
111 | char * | ||
112 | ssh_gssapi_client_mechanisms(const char *host, const char *client, | ||
113 | const char *kex) { | ||
114 | gss_OID_set gss_supported = NULL; | ||
115 | OM_uint32 min_status; | ||
116 | |||
117 | if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) | ||
118 | return NULL; | ||
119 | |||
120 | return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, | ||
121 | host, client, kex); | ||
122 | } | ||
123 | |||
124 | char * | ||
125 | ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, | ||
126 | const char *host, const char *client, const char *kex) { | ||
127 | struct sshbuf *buf = NULL; | ||
128 | size_t i; | ||
129 | int r = SSH_ERR_ALLOC_FAIL; | ||
130 | int oidpos, enclen; | ||
131 | char *mechs, *encoded; | ||
132 | u_char digest[SSH_DIGEST_MAX_LENGTH]; | ||
133 | char deroid[2]; | ||
134 | struct ssh_digest_ctx *md = NULL; | ||
135 | char *s, *cp, *p; | ||
136 | |||
137 | if (gss_enc2oid != NULL) { | ||
138 | for (i = 0; gss_enc2oid[i].encoded != NULL; i++) | ||
139 | free(gss_enc2oid[i].encoded); | ||
140 | free(gss_enc2oid); | ||
141 | } | ||
142 | |||
143 | gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * | ||
144 | (gss_supported->count + 1)); | ||
145 | |||
146 | if ((buf = sshbuf_new()) == NULL) | ||
147 | fatal("%s: sshbuf_new failed", __func__); | ||
148 | |||
149 | oidpos = 0; | ||
150 | s = cp = xstrdup(kex); | ||
151 | for (i = 0; i < gss_supported->count; i++) { | ||
152 | if (gss_supported->elements[i].length < 128 && | ||
153 | (*check)(NULL, &(gss_supported->elements[i]), host, client)) { | ||
154 | |||
155 | deroid[0] = SSH_GSS_OIDTYPE; | ||
156 | deroid[1] = gss_supported->elements[i].length; | ||
157 | |||
158 | if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || | ||
159 | (r = ssh_digest_update(md, deroid, 2)) != 0 || | ||
160 | (r = ssh_digest_update(md, | ||
161 | gss_supported->elements[i].elements, | ||
162 | gss_supported->elements[i].length)) != 0 || | ||
163 | (r = ssh_digest_final(md, digest, sizeof(digest))) != 0) | ||
164 | fatal("%s: digest failed: %s", __func__, | ||
165 | ssh_err(r)); | ||
166 | ssh_digest_free(md); | ||
167 | md = NULL; | ||
168 | |||
169 | encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5) | ||
170 | * 2); | ||
171 | enclen = __b64_ntop(digest, | ||
172 | ssh_digest_bytes(SSH_DIGEST_MD5), encoded, | ||
173 | ssh_digest_bytes(SSH_DIGEST_MD5) * 2); | ||
174 | |||
175 | cp = strncpy(s, kex, strlen(kex)); | ||
176 | for ((p = strsep(&cp, ",")); p && *p != '\0'; | ||
177 | (p = strsep(&cp, ","))) { | ||
178 | if (sshbuf_len(buf) != 0 && | ||
179 | (r = sshbuf_put_u8(buf, ',')) != 0) | ||
180 | fatal("%s: sshbuf_put_u8 error: %s", | ||
181 | __func__, ssh_err(r)); | ||
182 | if ((r = sshbuf_put(buf, p, strlen(p))) != 0 || | ||
183 | (r = sshbuf_put(buf, encoded, enclen)) != 0) | ||
184 | fatal("%s: sshbuf_put error: %s", | ||
185 | __func__, ssh_err(r)); | ||
186 | } | ||
187 | |||
188 | gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); | ||
189 | gss_enc2oid[oidpos].encoded = encoded; | ||
190 | oidpos++; | ||
191 | } | ||
192 | } | ||
193 | free(s); | ||
194 | gss_enc2oid[oidpos].oid = NULL; | ||
195 | gss_enc2oid[oidpos].encoded = NULL; | ||
196 | |||
197 | if ((mechs = sshbuf_dup_string(buf)) == NULL) | ||
198 | fatal("%s: sshbuf_dup_string failed", __func__); | ||
199 | |||
200 | sshbuf_free(buf); | ||
201 | |||
202 | if (strlen(mechs) == 0) { | ||
203 | free(mechs); | ||
204 | mechs = NULL; | ||
205 | } | ||
206 | |||
207 | return (mechs); | ||
208 | } | ||
209 | |||
210 | gss_OID | ||
211 | ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { | ||
212 | int i = 0; | ||
213 | |||
214 | #define SKIP_KEX_NAME(type) \ | ||
215 | case type: \ | ||
216 | if (strlen(name) < sizeof(type##_ID)) \ | ||
217 | return GSS_C_NO_OID; \ | ||
218 | name += sizeof(type##_ID) - 1; \ | ||
219 | break; | ||
220 | |||
221 | switch (kex_type) { | ||
222 | SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1) | ||
223 | SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1) | ||
224 | SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256) | ||
225 | SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512) | ||
226 | SKIP_KEX_NAME(KEX_GSS_GEX_SHA1) | ||
227 | SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256) | ||
228 | SKIP_KEX_NAME(KEX_GSS_C25519_SHA256) | ||
229 | default: | ||
230 | return GSS_C_NO_OID; | ||
231 | } | ||
232 | |||
233 | #undef SKIP_KEX_NAME | ||
234 | |||
235 | while (gss_enc2oid[i].encoded != NULL && | ||
236 | strcmp(name, gss_enc2oid[i].encoded) != 0) | ||
237 | i++; | ||
238 | |||
239 | if (gss_enc2oid[i].oid != NULL && ctx != NULL) | ||
240 | ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); | ||
241 | |||
242 | return gss_enc2oid[i].oid; | ||
243 | } | ||
244 | |||
65 | /* Check that the OID in a data stream matches that in the context */ | 245 | /* Check that the OID in a data stream matches that in the context */ |
66 | int | 246 | int |
67 | ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) | 247 | ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) |
@@ -218,7 +398,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, | |||
218 | } | 398 | } |
219 | 399 | ||
220 | ctx->major = gss_init_sec_context(&ctx->minor, | 400 | ctx->major = gss_init_sec_context(&ctx->minor, |
221 | GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, | 401 | ctx->client_creds, &ctx->context, ctx->name, ctx->oid, |
222 | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, | 402 | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, |
223 | 0, NULL, recv_tok, NULL, send_tok, flags, NULL); | 403 | 0, NULL, recv_tok, NULL, send_tok, flags, NULL); |
224 | 404 | ||
@@ -248,8 +428,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) | |||
248 | } | 428 | } |
249 | 429 | ||
250 | OM_uint32 | 430 | OM_uint32 |
431 | ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) | ||
432 | { | ||
433 | gss_buffer_desc gssbuf; | ||
434 | gss_name_t gssname; | ||
435 | OM_uint32 status; | ||
436 | gss_OID_set oidset; | ||
437 | |||
438 | gssbuf.value = (void *) name; | ||
439 | gssbuf.length = strlen(gssbuf.value); | ||
440 | |||
441 | gss_create_empty_oid_set(&status, &oidset); | ||
442 | gss_add_oid_set_member(&status, ctx->oid, &oidset); | ||
443 | |||
444 | ctx->major = gss_import_name(&ctx->minor, &gssbuf, | ||
445 | GSS_C_NT_USER_NAME, &gssname); | ||
446 | |||
447 | if (!ctx->major) | ||
448 | ctx->major = gss_acquire_cred(&ctx->minor, | ||
449 | gssname, 0, oidset, GSS_C_INITIATE, | ||
450 | &ctx->client_creds, NULL, NULL); | ||
451 | |||
452 | gss_release_name(&status, &gssname); | ||
453 | gss_release_oid_set(&status, &oidset); | ||
454 | |||
455 | if (ctx->major) | ||
456 | ssh_gssapi_error(ctx); | ||
457 | |||
458 | return(ctx->major); | ||
459 | } | ||
460 | |||
461 | OM_uint32 | ||
251 | ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | 462 | ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) |
252 | { | 463 | { |
464 | if (ctx == NULL) | ||
465 | return -1; | ||
466 | |||
253 | if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, | 467 | if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, |
254 | GSS_C_QOP_DEFAULT, buffer, hash))) | 468 | GSS_C_QOP_DEFAULT, buffer, hash))) |
255 | ssh_gssapi_error(ctx); | 469 | ssh_gssapi_error(ctx); |
@@ -257,6 +471,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | |||
257 | return (ctx->major); | 471 | return (ctx->major); |
258 | } | 472 | } |
259 | 473 | ||
474 | /* Priviledged when used by server */ | ||
475 | OM_uint32 | ||
476 | ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) | ||
477 | { | ||
478 | if (ctx == NULL) | ||
479 | return -1; | ||
480 | |||
481 | ctx->major = gss_verify_mic(&ctx->minor, ctx->context, | ||
482 | gssbuf, gssmic, NULL); | ||
483 | |||
484 | return (ctx->major); | ||
485 | } | ||
486 | |||
260 | void | 487 | void |
261 | ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, | 488 | ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, |
262 | const char *context) | 489 | const char *context) |
@@ -273,11 +500,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, | |||
273 | } | 500 | } |
274 | 501 | ||
275 | int | 502 | int |
276 | ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | 503 | ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, |
504 | const char *client) | ||
277 | { | 505 | { |
278 | gss_buffer_desc token = GSS_C_EMPTY_BUFFER; | 506 | gss_buffer_desc token = GSS_C_EMPTY_BUFFER; |
279 | OM_uint32 major, minor; | 507 | OM_uint32 major, minor; |
280 | gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; | 508 | gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; |
509 | Gssctxt *intctx = NULL; | ||
510 | |||
511 | if (ctx == NULL) | ||
512 | ctx = &intctx; | ||
281 | 513 | ||
282 | /* RFC 4462 says we MUST NOT do SPNEGO */ | 514 | /* RFC 4462 says we MUST NOT do SPNEGO */ |
283 | if (oid->length == spnego_oid.length && | 515 | if (oid->length == spnego_oid.length && |
@@ -287,6 +519,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |||
287 | ssh_gssapi_build_ctx(ctx); | 519 | ssh_gssapi_build_ctx(ctx); |
288 | ssh_gssapi_set_oid(*ctx, oid); | 520 | ssh_gssapi_set_oid(*ctx, oid); |
289 | major = ssh_gssapi_import_name(*ctx, host); | 521 | major = ssh_gssapi_import_name(*ctx, host); |
522 | |||
523 | if (!GSS_ERROR(major) && client) | ||
524 | major = ssh_gssapi_client_identity(*ctx, client); | ||
525 | |||
290 | if (!GSS_ERROR(major)) { | 526 | if (!GSS_ERROR(major)) { |
291 | major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, | 527 | major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, |
292 | NULL); | 528 | NULL); |
@@ -296,10 +532,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |||
296 | GSS_C_NO_BUFFER); | 532 | GSS_C_NO_BUFFER); |
297 | } | 533 | } |
298 | 534 | ||
299 | if (GSS_ERROR(major)) | 535 | if (GSS_ERROR(major) || intctx != NULL) |
300 | ssh_gssapi_delete_ctx(ctx); | 536 | ssh_gssapi_delete_ctx(ctx); |
301 | 537 | ||
302 | return (!GSS_ERROR(major)); | 538 | return (!GSS_ERROR(major)); |
303 | } | 539 | } |
304 | 540 | ||
541 | int | ||
542 | ssh_gssapi_credentials_updated(Gssctxt *ctxt) { | ||
543 | static gss_name_t saved_name = GSS_C_NO_NAME; | ||
544 | static OM_uint32 saved_lifetime = 0; | ||
545 | static gss_OID saved_mech = GSS_C_NO_OID; | ||
546 | static gss_name_t name; | ||
547 | static OM_uint32 last_call = 0; | ||
548 | OM_uint32 lifetime, now, major, minor; | ||
549 | int equal; | ||
550 | |||
551 | now = time(NULL); | ||
552 | |||
553 | if (ctxt) { | ||
554 | debug("Rekey has happened - updating saved versions"); | ||
555 | |||
556 | if (saved_name != GSS_C_NO_NAME) | ||
557 | gss_release_name(&minor, &saved_name); | ||
558 | |||
559 | major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | ||
560 | &saved_name, &saved_lifetime, NULL, NULL); | ||
561 | |||
562 | if (!GSS_ERROR(major)) { | ||
563 | saved_mech = ctxt->oid; | ||
564 | saved_lifetime+= now; | ||
565 | } else { | ||
566 | /* Handle the error */ | ||
567 | } | ||
568 | return 0; | ||
569 | } | ||
570 | |||
571 | if (now - last_call < 10) | ||
572 | return 0; | ||
573 | |||
574 | last_call = now; | ||
575 | |||
576 | if (saved_mech == GSS_C_NO_OID) | ||
577 | return 0; | ||
578 | |||
579 | major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | ||
580 | &name, &lifetime, NULL, NULL); | ||
581 | if (major == GSS_S_CREDENTIALS_EXPIRED) | ||
582 | return 0; | ||
583 | else if (GSS_ERROR(major)) | ||
584 | return 0; | ||
585 | |||
586 | major = gss_compare_name(&minor, saved_name, name, &equal); | ||
587 | gss_release_name(&minor, &name); | ||
588 | if (GSS_ERROR(major)) | ||
589 | return 0; | ||
590 | |||
591 | if (equal && (saved_lifetime < lifetime + now - 10)) | ||
592 | return 1; | ||
593 | |||
594 | return 0; | ||
595 | } | ||
596 | |||
305 | #endif /* GSSAPI */ | 597 | #endif /* GSSAPI */ |