diff options
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 */ |