diff options
author | Simon Wilkinson <simon@sxw.org.uk> | 2014-02-09 16:09:48 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2016-01-04 13:43:45 +0000 |
commit | d6cfd64ea0a567d88152270a94be6bb2a78daeb9 (patch) | |
tree | d0952dba1a30c4bf5c4301613e18ac1dea67d998 /gss-genr.c | |
parent | 651211fd4a199b299540c00c54a46e27fadb04be (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.
Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242
Last-Updated: 2016-01-04
Patch-Name: gssapi.patch
Diffstat (limited to 'gss-genr.c')
-rw-r--r-- | gss-genr.c | 275 |
1 files changed, 271 insertions, 4 deletions
diff --git a/gss-genr.c b/gss-genr.c index d617d600a..b4eca3feb 100644 --- a/gss-genr.c +++ b/gss-genr.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* $OpenBSD: gss-genr.c,v 1.23 2015/01/20 23:14:00 deraadt Exp $ */ | 1 | /* $OpenBSD: gss-genr.c,v 1.23 2015/01/20 23:14:00 deraadt 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,167 @@ | |||
41 | #include "buffer.h" | 41 | #include "buffer.h" |
42 | #include "log.h" | 42 | #include "log.h" |
43 | #include "ssh2.h" | 43 | #include "ssh2.h" |
44 | #include "cipher.h" | ||
45 | #include "key.h" | ||
46 | #include "kex.h" | ||
47 | #include <openssl/evp.h> | ||
44 | 48 | ||
45 | #include "ssh-gss.h" | 49 | #include "ssh-gss.h" |
46 | 50 | ||
47 | extern u_char *session_id2; | 51 | extern u_char *session_id2; |
48 | extern u_int session_id2_len; | 52 | extern u_int session_id2_len; |
49 | 53 | ||
54 | typedef struct { | ||
55 | char *encoded; | ||
56 | gss_OID oid; | ||
57 | } ssh_gss_kex_mapping; | ||
58 | |||
59 | /* | ||
60 | * XXX - It would be nice to find a more elegant way of handling the | ||
61 | * XXX passing of the key exchange context to the userauth routines | ||
62 | */ | ||
63 | |||
64 | Gssctxt *gss_kex_context = NULL; | ||
65 | |||
66 | static ssh_gss_kex_mapping *gss_enc2oid = NULL; | ||
67 | |||
68 | int | ||
69 | ssh_gssapi_oid_table_ok(void) { | ||
70 | return (gss_enc2oid != NULL); | ||
71 | } | ||
72 | |||
73 | /* | ||
74 | * Return a list of the gss-group1-sha1 mechanisms supported by this program | ||
75 | * | ||
76 | * We test mechanisms to ensure that we can use them, to avoid starting | ||
77 | * a key exchange with a bad mechanism | ||
78 | */ | ||
79 | |||
80 | char * | ||
81 | ssh_gssapi_client_mechanisms(const char *host, const char *client) { | ||
82 | gss_OID_set gss_supported; | ||
83 | OM_uint32 min_status; | ||
84 | |||
85 | if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) | ||
86 | return NULL; | ||
87 | |||
88 | return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, | ||
89 | host, client)); | ||
90 | } | ||
91 | |||
92 | char * | ||
93 | ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, | ||
94 | const char *host, const char *client) { | ||
95 | Buffer buf; | ||
96 | size_t i; | ||
97 | int oidpos, enclen; | ||
98 | char *mechs, *encoded; | ||
99 | u_char digest[EVP_MAX_MD_SIZE]; | ||
100 | char deroid[2]; | ||
101 | const EVP_MD *evp_md = EVP_md5(); | ||
102 | EVP_MD_CTX md; | ||
103 | |||
104 | if (gss_enc2oid != NULL) { | ||
105 | for (i = 0; gss_enc2oid[i].encoded != NULL; i++) | ||
106 | free(gss_enc2oid[i].encoded); | ||
107 | free(gss_enc2oid); | ||
108 | } | ||
109 | |||
110 | gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * | ||
111 | (gss_supported->count + 1)); | ||
112 | |||
113 | buffer_init(&buf); | ||
114 | |||
115 | oidpos = 0; | ||
116 | for (i = 0; i < gss_supported->count; i++) { | ||
117 | if (gss_supported->elements[i].length < 128 && | ||
118 | (*check)(NULL, &(gss_supported->elements[i]), host, client)) { | ||
119 | |||
120 | deroid[0] = SSH_GSS_OIDTYPE; | ||
121 | deroid[1] = gss_supported->elements[i].length; | ||
122 | |||
123 | EVP_DigestInit(&md, evp_md); | ||
124 | EVP_DigestUpdate(&md, deroid, 2); | ||
125 | EVP_DigestUpdate(&md, | ||
126 | gss_supported->elements[i].elements, | ||
127 | gss_supported->elements[i].length); | ||
128 | EVP_DigestFinal(&md, digest, NULL); | ||
129 | |||
130 | encoded = xmalloc(EVP_MD_size(evp_md) * 2); | ||
131 | enclen = __b64_ntop(digest, EVP_MD_size(evp_md), | ||
132 | encoded, EVP_MD_size(evp_md) * 2); | ||
133 | |||
134 | if (oidpos != 0) | ||
135 | buffer_put_char(&buf, ','); | ||
136 | |||
137 | buffer_append(&buf, KEX_GSS_GEX_SHA1_ID, | ||
138 | sizeof(KEX_GSS_GEX_SHA1_ID) - 1); | ||
139 | buffer_append(&buf, encoded, enclen); | ||
140 | buffer_put_char(&buf, ','); | ||
141 | buffer_append(&buf, KEX_GSS_GRP1_SHA1_ID, | ||
142 | sizeof(KEX_GSS_GRP1_SHA1_ID) - 1); | ||
143 | buffer_append(&buf, encoded, enclen); | ||
144 | buffer_put_char(&buf, ','); | ||
145 | buffer_append(&buf, KEX_GSS_GRP14_SHA1_ID, | ||
146 | sizeof(KEX_GSS_GRP14_SHA1_ID) - 1); | ||
147 | buffer_append(&buf, encoded, enclen); | ||
148 | |||
149 | gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); | ||
150 | gss_enc2oid[oidpos].encoded = encoded; | ||
151 | oidpos++; | ||
152 | } | ||
153 | } | ||
154 | gss_enc2oid[oidpos].oid = NULL; | ||
155 | gss_enc2oid[oidpos].encoded = NULL; | ||
156 | |||
157 | buffer_put_char(&buf, '\0'); | ||
158 | |||
159 | mechs = xmalloc(buffer_len(&buf)); | ||
160 | buffer_get(&buf, mechs, buffer_len(&buf)); | ||
161 | buffer_free(&buf); | ||
162 | |||
163 | if (strlen(mechs) == 0) { | ||
164 | free(mechs); | ||
165 | mechs = NULL; | ||
166 | } | ||
167 | |||
168 | return (mechs); | ||
169 | } | ||
170 | |||
171 | gss_OID | ||
172 | ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { | ||
173 | int i = 0; | ||
174 | |||
175 | switch (kex_type) { | ||
176 | case KEX_GSS_GRP1_SHA1: | ||
177 | if (strlen(name) < sizeof(KEX_GSS_GRP1_SHA1_ID)) | ||
178 | return GSS_C_NO_OID; | ||
179 | name += sizeof(KEX_GSS_GRP1_SHA1_ID) - 1; | ||
180 | break; | ||
181 | case KEX_GSS_GRP14_SHA1: | ||
182 | if (strlen(name) < sizeof(KEX_GSS_GRP14_SHA1_ID)) | ||
183 | return GSS_C_NO_OID; | ||
184 | name += sizeof(KEX_GSS_GRP14_SHA1_ID) - 1; | ||
185 | break; | ||
186 | case KEX_GSS_GEX_SHA1: | ||
187 | if (strlen(name) < sizeof(KEX_GSS_GEX_SHA1_ID)) | ||
188 | return GSS_C_NO_OID; | ||
189 | name += sizeof(KEX_GSS_GEX_SHA1_ID) - 1; | ||
190 | break; | ||
191 | default: | ||
192 | return GSS_C_NO_OID; | ||
193 | } | ||
194 | |||
195 | while (gss_enc2oid[i].encoded != NULL && | ||
196 | strcmp(name, gss_enc2oid[i].encoded) != 0) | ||
197 | i++; | ||
198 | |||
199 | if (gss_enc2oid[i].oid != NULL && ctx != NULL) | ||
200 | ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); | ||
201 | |||
202 | return gss_enc2oid[i].oid; | ||
203 | } | ||
204 | |||
50 | /* Check that the OID in a data stream matches that in the context */ | 205 | /* Check that the OID in a data stream matches that in the context */ |
51 | int | 206 | int |
52 | ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) | 207 | ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) |
@@ -199,7 +354,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, | |||
199 | } | 354 | } |
200 | 355 | ||
201 | ctx->major = gss_init_sec_context(&ctx->minor, | 356 | ctx->major = gss_init_sec_context(&ctx->minor, |
202 | GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, | 357 | ctx->client_creds, &ctx->context, ctx->name, ctx->oid, |
203 | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, | 358 | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, |
204 | 0, NULL, recv_tok, NULL, send_tok, flags, NULL); | 359 | 0, NULL, recv_tok, NULL, send_tok, flags, NULL); |
205 | 360 | ||
@@ -229,8 +384,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) | |||
229 | } | 384 | } |
230 | 385 | ||
231 | OM_uint32 | 386 | OM_uint32 |
387 | ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) | ||
388 | { | ||
389 | gss_buffer_desc gssbuf; | ||
390 | gss_name_t gssname; | ||
391 | OM_uint32 status; | ||
392 | gss_OID_set oidset; | ||
393 | |||
394 | gssbuf.value = (void *) name; | ||
395 | gssbuf.length = strlen(gssbuf.value); | ||
396 | |||
397 | gss_create_empty_oid_set(&status, &oidset); | ||
398 | gss_add_oid_set_member(&status, ctx->oid, &oidset); | ||
399 | |||
400 | ctx->major = gss_import_name(&ctx->minor, &gssbuf, | ||
401 | GSS_C_NT_USER_NAME, &gssname); | ||
402 | |||
403 | if (!ctx->major) | ||
404 | ctx->major = gss_acquire_cred(&ctx->minor, | ||
405 | gssname, 0, oidset, GSS_C_INITIATE, | ||
406 | &ctx->client_creds, NULL, NULL); | ||
407 | |||
408 | gss_release_name(&status, &gssname); | ||
409 | gss_release_oid_set(&status, &oidset); | ||
410 | |||
411 | if (ctx->major) | ||
412 | ssh_gssapi_error(ctx); | ||
413 | |||
414 | return(ctx->major); | ||
415 | } | ||
416 | |||
417 | OM_uint32 | ||
232 | ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | 418 | ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) |
233 | { | 419 | { |
420 | if (ctx == NULL) | ||
421 | return -1; | ||
422 | |||
234 | if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, | 423 | if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, |
235 | GSS_C_QOP_DEFAULT, buffer, hash))) | 424 | GSS_C_QOP_DEFAULT, buffer, hash))) |
236 | ssh_gssapi_error(ctx); | 425 | ssh_gssapi_error(ctx); |
@@ -238,6 +427,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | |||
238 | return (ctx->major); | 427 | return (ctx->major); |
239 | } | 428 | } |
240 | 429 | ||
430 | /* Priviledged when used by server */ | ||
431 | OM_uint32 | ||
432 | ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) | ||
433 | { | ||
434 | if (ctx == NULL) | ||
435 | return -1; | ||
436 | |||
437 | ctx->major = gss_verify_mic(&ctx->minor, ctx->context, | ||
438 | gssbuf, gssmic, NULL); | ||
439 | |||
440 | return (ctx->major); | ||
441 | } | ||
442 | |||
241 | void | 443 | void |
242 | ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, | 444 | ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, |
243 | const char *context) | 445 | const char *context) |
@@ -251,11 +453,16 @@ ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, | |||
251 | } | 453 | } |
252 | 454 | ||
253 | int | 455 | int |
254 | ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | 456 | ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, |
457 | const char *client) | ||
255 | { | 458 | { |
256 | gss_buffer_desc token = GSS_C_EMPTY_BUFFER; | 459 | gss_buffer_desc token = GSS_C_EMPTY_BUFFER; |
257 | OM_uint32 major, minor; | 460 | OM_uint32 major, minor; |
258 | gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; | 461 | gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; |
462 | Gssctxt *intctx = NULL; | ||
463 | |||
464 | if (ctx == NULL) | ||
465 | ctx = &intctx; | ||
259 | 466 | ||
260 | /* RFC 4462 says we MUST NOT do SPNEGO */ | 467 | /* RFC 4462 says we MUST NOT do SPNEGO */ |
261 | if (oid->length == spnego_oid.length && | 468 | if (oid->length == spnego_oid.length && |
@@ -265,6 +472,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |||
265 | ssh_gssapi_build_ctx(ctx); | 472 | ssh_gssapi_build_ctx(ctx); |
266 | ssh_gssapi_set_oid(*ctx, oid); | 473 | ssh_gssapi_set_oid(*ctx, oid); |
267 | major = ssh_gssapi_import_name(*ctx, host); | 474 | major = ssh_gssapi_import_name(*ctx, host); |
475 | |||
476 | if (!GSS_ERROR(major) && client) | ||
477 | major = ssh_gssapi_client_identity(*ctx, client); | ||
478 | |||
268 | if (!GSS_ERROR(major)) { | 479 | if (!GSS_ERROR(major)) { |
269 | major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, | 480 | major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, |
270 | NULL); | 481 | NULL); |
@@ -274,10 +485,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |||
274 | GSS_C_NO_BUFFER); | 485 | GSS_C_NO_BUFFER); |
275 | } | 486 | } |
276 | 487 | ||
277 | if (GSS_ERROR(major)) | 488 | if (GSS_ERROR(major) || intctx != NULL) |
278 | ssh_gssapi_delete_ctx(ctx); | 489 | ssh_gssapi_delete_ctx(ctx); |
279 | 490 | ||
280 | return (!GSS_ERROR(major)); | 491 | return (!GSS_ERROR(major)); |
281 | } | 492 | } |
282 | 493 | ||
494 | int | ||
495 | ssh_gssapi_credentials_updated(Gssctxt *ctxt) { | ||
496 | static gss_name_t saved_name = GSS_C_NO_NAME; | ||
497 | static OM_uint32 saved_lifetime = 0; | ||
498 | static gss_OID saved_mech = GSS_C_NO_OID; | ||
499 | static gss_name_t name; | ||
500 | static OM_uint32 last_call = 0; | ||
501 | OM_uint32 lifetime, now, major, minor; | ||
502 | int equal; | ||
503 | |||
504 | now = time(NULL); | ||
505 | |||
506 | if (ctxt) { | ||
507 | debug("Rekey has happened - updating saved versions"); | ||
508 | |||
509 | if (saved_name != GSS_C_NO_NAME) | ||
510 | gss_release_name(&minor, &saved_name); | ||
511 | |||
512 | major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | ||
513 | &saved_name, &saved_lifetime, NULL, NULL); | ||
514 | |||
515 | if (!GSS_ERROR(major)) { | ||
516 | saved_mech = ctxt->oid; | ||
517 | saved_lifetime+= now; | ||
518 | } else { | ||
519 | /* Handle the error */ | ||
520 | } | ||
521 | return 0; | ||
522 | } | ||
523 | |||
524 | if (now - last_call < 10) | ||
525 | return 0; | ||
526 | |||
527 | last_call = now; | ||
528 | |||
529 | if (saved_mech == GSS_C_NO_OID) | ||
530 | return 0; | ||
531 | |||
532 | major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | ||
533 | &name, &lifetime, NULL, NULL); | ||
534 | if (major == GSS_S_CREDENTIALS_EXPIRED) | ||
535 | return 0; | ||
536 | else if (GSS_ERROR(major)) | ||
537 | return 0; | ||
538 | |||
539 | major = gss_compare_name(&minor, saved_name, name, &equal); | ||
540 | gss_release_name(&minor, &name); | ||
541 | if (GSS_ERROR(major)) | ||
542 | return 0; | ||
543 | |||
544 | if (equal && (saved_lifetime < lifetime + now - 10)) | ||
545 | return 1; | ||
546 | |||
547 | return 0; | ||
548 | } | ||
549 | |||
283 | #endif /* GSSAPI */ | 550 | #endif /* GSSAPI */ |