diff options
author | Simon Wilkinson <simon@sxw.org.uk> | 2014-02-09 16:09:48 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2016-12-23 11:24:59 +0000 |
commit | 9f717de15a8e113f7c6a3db52d75ce0172885f95 (patch) | |
tree | 75236e36100f74ec69d8e8d38543224237ceacf1 /kexgssc.c | |
parent | 971a7653746a6972b907dfe0ce139c06e4a6f482 (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-08-07
Patch-Name: gssapi.patch
Diffstat (limited to 'kexgssc.c')
-rw-r--r-- | kexgssc.c | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/kexgssc.c b/kexgssc.c new file mode 100644 index 000000000..10447f2b0 --- /dev/null +++ b/kexgssc.c | |||
@@ -0,0 +1,338 @@ | |||
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 | #ifdef GSSAPI | ||
28 | |||
29 | #include "includes.h" | ||
30 | |||
31 | #include <openssl/crypto.h> | ||
32 | #include <openssl/bn.h> | ||
33 | |||
34 | #include <string.h> | ||
35 | |||
36 | #include "xmalloc.h" | ||
37 | #include "buffer.h" | ||
38 | #include "ssh2.h" | ||
39 | #include "key.h" | ||
40 | #include "cipher.h" | ||
41 | #include "kex.h" | ||
42 | #include "log.h" | ||
43 | #include "packet.h" | ||
44 | #include "dh.h" | ||
45 | #include "digest.h" | ||
46 | |||
47 | #include "ssh-gss.h" | ||
48 | |||
49 | int | ||
50 | kexgss_client(struct ssh *ssh) { | ||
51 | gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; | ||
52 | gss_buffer_desc recv_tok, gssbuf, msg_tok, *token_ptr; | ||
53 | Gssctxt *ctxt; | ||
54 | OM_uint32 maj_status, min_status, ret_flags; | ||
55 | u_int klen, kout, slen = 0, strlen; | ||
56 | DH *dh; | ||
57 | BIGNUM *dh_server_pub = NULL; | ||
58 | BIGNUM *shared_secret = NULL; | ||
59 | BIGNUM *p = NULL; | ||
60 | BIGNUM *g = NULL; | ||
61 | u_char *kbuf; | ||
62 | u_char *serverhostkey = NULL; | ||
63 | u_char *empty = ""; | ||
64 | char *msg; | ||
65 | int type = 0; | ||
66 | int first = 1; | ||
67 | int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX; | ||
68 | u_char hash[SSH_DIGEST_MAX_LENGTH]; | ||
69 | size_t hashlen; | ||
70 | |||
71 | /* Initialise our GSSAPI world */ | ||
72 | ssh_gssapi_build_ctx(&ctxt); | ||
73 | if (ssh_gssapi_id_kex(ctxt, ssh->kex->name, ssh->kex->kex_type) | ||
74 | == GSS_C_NO_OID) | ||
75 | fatal("Couldn't identify host exchange"); | ||
76 | |||
77 | if (ssh_gssapi_import_name(ctxt, ssh->kex->gss_host)) | ||
78 | fatal("Couldn't import hostname"); | ||
79 | |||
80 | if (ssh->kex->gss_client && | ||
81 | ssh_gssapi_client_identity(ctxt, ssh->kex->gss_client)) | ||
82 | fatal("Couldn't acquire client credentials"); | ||
83 | |||
84 | switch (ssh->kex->kex_type) { | ||
85 | case KEX_GSS_GRP1_SHA1: | ||
86 | dh = dh_new_group1(); | ||
87 | break; | ||
88 | case KEX_GSS_GRP14_SHA1: | ||
89 | dh = dh_new_group14(); | ||
90 | break; | ||
91 | case KEX_GSS_GEX_SHA1: | ||
92 | debug("Doing group exchange\n"); | ||
93 | nbits = dh_estimate(ssh->kex->we_need * 8); | ||
94 | packet_start(SSH2_MSG_KEXGSS_GROUPREQ); | ||
95 | packet_put_int(min); | ||
96 | packet_put_int(nbits); | ||
97 | packet_put_int(max); | ||
98 | |||
99 | packet_send(); | ||
100 | |||
101 | packet_read_expect(SSH2_MSG_KEXGSS_GROUP); | ||
102 | |||
103 | if ((p = BN_new()) == NULL) | ||
104 | fatal("BN_new() failed"); | ||
105 | packet_get_bignum2(p); | ||
106 | if ((g = BN_new()) == NULL) | ||
107 | fatal("BN_new() failed"); | ||
108 | packet_get_bignum2(g); | ||
109 | packet_check_eom(); | ||
110 | |||
111 | if (BN_num_bits(p) < min || BN_num_bits(p) > max) | ||
112 | fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", | ||
113 | min, BN_num_bits(p), max); | ||
114 | |||
115 | dh = dh_new_group(g, p); | ||
116 | break; | ||
117 | default: | ||
118 | fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); | ||
119 | } | ||
120 | |||
121 | /* Step 1 - e is dh->pub_key */ | ||
122 | dh_gen_key(dh, ssh->kex->we_need * 8); | ||
123 | |||
124 | /* This is f, we initialise it now to make life easier */ | ||
125 | dh_server_pub = BN_new(); | ||
126 | if (dh_server_pub == NULL) | ||
127 | fatal("dh_server_pub == NULL"); | ||
128 | |||
129 | token_ptr = GSS_C_NO_BUFFER; | ||
130 | |||
131 | do { | ||
132 | debug("Calling gss_init_sec_context"); | ||
133 | |||
134 | maj_status = ssh_gssapi_init_ctx(ctxt, | ||
135 | ssh->kex->gss_deleg_creds, token_ptr, &send_tok, | ||
136 | &ret_flags); | ||
137 | |||
138 | if (GSS_ERROR(maj_status)) { | ||
139 | if (send_tok.length != 0) { | ||
140 | packet_start(SSH2_MSG_KEXGSS_CONTINUE); | ||
141 | packet_put_string(send_tok.value, | ||
142 | send_tok.length); | ||
143 | } | ||
144 | fatal("gss_init_context failed"); | ||
145 | } | ||
146 | |||
147 | /* If we've got an old receive buffer get rid of it */ | ||
148 | if (token_ptr != GSS_C_NO_BUFFER) | ||
149 | free(recv_tok.value); | ||
150 | |||
151 | if (maj_status == GSS_S_COMPLETE) { | ||
152 | /* If mutual state flag is not true, kex fails */ | ||
153 | if (!(ret_flags & GSS_C_MUTUAL_FLAG)) | ||
154 | fatal("Mutual authentication failed"); | ||
155 | |||
156 | /* If integ avail flag is not true kex fails */ | ||
157 | if (!(ret_flags & GSS_C_INTEG_FLAG)) | ||
158 | fatal("Integrity check failed"); | ||
159 | } | ||
160 | |||
161 | /* | ||
162 | * If we have data to send, then the last message that we | ||
163 | * received cannot have been a 'complete'. | ||
164 | */ | ||
165 | if (send_tok.length != 0) { | ||
166 | if (first) { | ||
167 | packet_start(SSH2_MSG_KEXGSS_INIT); | ||
168 | packet_put_string(send_tok.value, | ||
169 | send_tok.length); | ||
170 | packet_put_bignum2(dh->pub_key); | ||
171 | first = 0; | ||
172 | } else { | ||
173 | packet_start(SSH2_MSG_KEXGSS_CONTINUE); | ||
174 | packet_put_string(send_tok.value, | ||
175 | send_tok.length); | ||
176 | } | ||
177 | packet_send(); | ||
178 | gss_release_buffer(&min_status, &send_tok); | ||
179 | |||
180 | /* If we've sent them data, they should reply */ | ||
181 | do { | ||
182 | type = packet_read(); | ||
183 | if (type == SSH2_MSG_KEXGSS_HOSTKEY) { | ||
184 | debug("Received KEXGSS_HOSTKEY"); | ||
185 | if (serverhostkey) | ||
186 | fatal("Server host key received more than once"); | ||
187 | serverhostkey = | ||
188 | packet_get_string(&slen); | ||
189 | } | ||
190 | } while (type == SSH2_MSG_KEXGSS_HOSTKEY); | ||
191 | |||
192 | switch (type) { | ||
193 | case SSH2_MSG_KEXGSS_CONTINUE: | ||
194 | debug("Received GSSAPI_CONTINUE"); | ||
195 | if (maj_status == GSS_S_COMPLETE) | ||
196 | fatal("GSSAPI Continue received from server when complete"); | ||
197 | recv_tok.value = packet_get_string(&strlen); | ||
198 | recv_tok.length = strlen; | ||
199 | break; | ||
200 | case SSH2_MSG_KEXGSS_COMPLETE: | ||
201 | debug("Received GSSAPI_COMPLETE"); | ||
202 | packet_get_bignum2(dh_server_pub); | ||
203 | msg_tok.value = packet_get_string(&strlen); | ||
204 | msg_tok.length = strlen; | ||
205 | |||
206 | /* Is there a token included? */ | ||
207 | if (packet_get_char()) { | ||
208 | recv_tok.value= | ||
209 | packet_get_string(&strlen); | ||
210 | recv_tok.length = strlen; | ||
211 | /* If we're already complete - protocol error */ | ||
212 | if (maj_status == GSS_S_COMPLETE) | ||
213 | packet_disconnect("Protocol error: received token when complete"); | ||
214 | } else { | ||
215 | /* No token included */ | ||
216 | if (maj_status != GSS_S_COMPLETE) | ||
217 | packet_disconnect("Protocol error: did not receive final token"); | ||
218 | } | ||
219 | break; | ||
220 | case SSH2_MSG_KEXGSS_ERROR: | ||
221 | debug("Received Error"); | ||
222 | maj_status = packet_get_int(); | ||
223 | min_status = packet_get_int(); | ||
224 | msg = packet_get_string(NULL); | ||
225 | (void) packet_get_string_ptr(NULL); | ||
226 | fatal("GSSAPI Error: \n%.400s",msg); | ||
227 | default: | ||
228 | packet_disconnect("Protocol error: didn't expect packet type %d", | ||
229 | type); | ||
230 | } | ||
231 | token_ptr = &recv_tok; | ||
232 | } else { | ||
233 | /* No data, and not complete */ | ||
234 | if (maj_status != GSS_S_COMPLETE) | ||
235 | fatal("Not complete, and no token output"); | ||
236 | } | ||
237 | } while (maj_status & GSS_S_CONTINUE_NEEDED); | ||
238 | |||
239 | /* | ||
240 | * We _must_ have received a COMPLETE message in reply from the | ||
241 | * server, which will have set dh_server_pub and msg_tok | ||
242 | */ | ||
243 | |||
244 | if (type != SSH2_MSG_KEXGSS_COMPLETE) | ||
245 | fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); | ||
246 | |||
247 | /* Check f in range [1, p-1] */ | ||
248 | if (!dh_pub_is_valid(dh, dh_server_pub)) | ||
249 | packet_disconnect("bad server public DH value"); | ||
250 | |||
251 | /* compute K=f^x mod p */ | ||
252 | klen = DH_size(dh); | ||
253 | kbuf = xmalloc(klen); | ||
254 | kout = DH_compute_key(kbuf, dh_server_pub, dh); | ||
255 | if (kout < 0) | ||
256 | fatal("DH_compute_key: failed"); | ||
257 | |||
258 | shared_secret = BN_new(); | ||
259 | if (shared_secret == NULL) | ||
260 | fatal("kexgss_client: BN_new failed"); | ||
261 | |||
262 | if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) | ||
263 | fatal("kexdh_client: BN_bin2bn failed"); | ||
264 | |||
265 | memset(kbuf, 0, klen); | ||
266 | free(kbuf); | ||
267 | |||
268 | hashlen = sizeof(hash); | ||
269 | switch (ssh->kex->kex_type) { | ||
270 | case KEX_GSS_GRP1_SHA1: | ||
271 | case KEX_GSS_GRP14_SHA1: | ||
272 | kex_dh_hash( | ||
273 | ssh->kex->hash_alg, | ||
274 | ssh->kex->client_version_string, | ||
275 | ssh->kex->server_version_string, | ||
276 | buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), | ||
277 | buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), | ||
278 | (serverhostkey ? serverhostkey : empty), slen, | ||
279 | dh->pub_key, /* e */ | ||
280 | dh_server_pub, /* f */ | ||
281 | shared_secret, /* K */ | ||
282 | hash, &hashlen | ||
283 | ); | ||
284 | break; | ||
285 | case KEX_GSS_GEX_SHA1: | ||
286 | kexgex_hash( | ||
287 | ssh->kex->hash_alg, | ||
288 | ssh->kex->client_version_string, | ||
289 | ssh->kex->server_version_string, | ||
290 | buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), | ||
291 | buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), | ||
292 | (serverhostkey ? serverhostkey : empty), slen, | ||
293 | min, nbits, max, | ||
294 | dh->p, dh->g, | ||
295 | dh->pub_key, | ||
296 | dh_server_pub, | ||
297 | shared_secret, | ||
298 | hash, &hashlen | ||
299 | ); | ||
300 | break; | ||
301 | default: | ||
302 | fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); | ||
303 | } | ||
304 | |||
305 | gssbuf.value = hash; | ||
306 | gssbuf.length = hashlen; | ||
307 | |||
308 | /* Verify that the hash matches the MIC we just got. */ | ||
309 | if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) | ||
310 | packet_disconnect("Hash's MIC didn't verify"); | ||
311 | |||
312 | free(msg_tok.value); | ||
313 | |||
314 | DH_free(dh); | ||
315 | free(serverhostkey); | ||
316 | BN_clear_free(dh_server_pub); | ||
317 | |||
318 | /* save session id */ | ||
319 | if (ssh->kex->session_id == NULL) { | ||
320 | ssh->kex->session_id_len = hashlen; | ||
321 | ssh->kex->session_id = xmalloc(ssh->kex->session_id_len); | ||
322 | memcpy(ssh->kex->session_id, hash, ssh->kex->session_id_len); | ||
323 | } | ||
324 | |||
325 | if (ssh->kex->gss_deleg_creds) | ||
326 | ssh_gssapi_credentials_updated(ctxt); | ||
327 | |||
328 | if (gss_kex_context == NULL) | ||
329 | gss_kex_context = ctxt; | ||
330 | else | ||
331 | ssh_gssapi_delete_ctx(&ctxt); | ||
332 | |||
333 | kex_derive_keys_bn(ssh, hash, hashlen, shared_secret); | ||
334 | BN_clear_free(shared_secret); | ||
335 | return kex_send_newkeys(ssh); | ||
336 | } | ||
337 | |||
338 | #endif /* GSSAPI */ | ||