1 /*
2 * OpenConnect (SSL + DTLS) VPN client
3 *
4 * Copyright © 2008-2015 Intel Corporation.
5 *
6 * Author: David Woodhouse <dwmw2@infradead.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 */
17
18 #include <config.h>
19
20 #include <errno.h>
21 #include <string.h>
22
23 #include "openconnect-internal.h"
24
print_gss_err(struct openconnect_info * vpninfo,const char * where,gss_OID mech,OM_uint32 err_maj,OM_uint32 err_min)25 static void print_gss_err(struct openconnect_info *vpninfo, const char *where,
26 gss_OID mech, OM_uint32 err_maj, OM_uint32 err_min)
27 {
28 OM_uint32 major, minor, msg_ctx = 0;
29 gss_buffer_desc status;
30
31 do {
32 major = gss_display_status(&minor, err_maj, GSS_C_GSS_CODE,
33 mech, &msg_ctx, &status);
34 if (GSS_ERROR(major))
35 break;
36 vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value);
37 gss_release_buffer(&minor, &status);
38 } while (msg_ctx);
39
40 msg_ctx = 0;
41 do {
42 major = gss_display_status(&minor, err_min, GSS_C_MECH_CODE,
43 mech, &msg_ctx, &status);
44 if (GSS_ERROR(major))
45 break;
46 vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value);
47 gss_release_buffer(&minor, &status);
48 } while (msg_ctx);
49 }
50
51 static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
52 static const gss_OID_desc gss_mech_spnego = {
53 6,
54 (void *)&spnego_OID
55 };
56
gssapi_setup(struct openconnect_info * vpninfo,struct http_auth_state * auth_state,const char * service,int proxy)57 static int gssapi_setup(struct openconnect_info *vpninfo, struct http_auth_state *auth_state,
58 const char *service, int proxy)
59 {
60 OM_uint32 major, minor;
61 gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
62 char *name;
63
64 if (asprintf(&name, "%s@%s", service,
65 proxy ? vpninfo->proxy : vpninfo->hostname) == -1)
66 return -ENOMEM;
67 token.length = strlen(name);
68 token.value = name;
69
70 major = gss_import_name(&minor, &token, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
71 &auth_state->gss_target_name);
72 free(name);
73 if (GSS_ERROR(major)) {
74 vpn_progress(vpninfo, PRG_ERR,
75 _("Error importing GSSAPI name for authentication:\n"));
76 print_gss_err(vpninfo, "gss_import_name()", GSS_C_NO_OID, major, minor);
77 return -EIO;
78 }
79 return 0;
80 }
81
82 #define GSSAPI_CONTINUE 2
83 #define GSSAPI_COMPLETE 3
84
gssapi_authorization(struct openconnect_info * vpninfo,int proxy,struct http_auth_state * auth_state,struct oc_text_buf * hdrbuf)85 int gssapi_authorization(struct openconnect_info *vpninfo, int proxy,
86 struct http_auth_state *auth_state,
87 struct oc_text_buf *hdrbuf)
88 {
89 OM_uint32 major, minor;
90 gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
91 gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
92 gss_OID mech = GSS_C_NO_OID;
93
94 if (auth_state->state == AUTH_AVAILABLE && gssapi_setup(vpninfo, auth_state, "HTTP", proxy)) {
95 auth_state->state = AUTH_FAILED;
96 return -EIO;
97 }
98
99 if (auth_state->challenge && *auth_state->challenge) {
100 int len = -EINVAL;
101 in.value = openconnect_base64_decode(&len, auth_state->challenge);
102 if (!in.value)
103 return len;
104 in.length = len;
105 } else if (auth_state->state > AUTH_AVAILABLE) {
106 /* This indicates failure. We were trying, but got an empty
107 'Proxy-Authorization: Negotiate' header back from the server
108 implying that we should start again... */
109 goto fail_gssapi;
110 }
111
112 major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
113 &auth_state->gss_context,
114 auth_state->gss_target_name,
115 (gss_OID)&gss_mech_spnego,
116 GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE,
117 GSS_C_NO_CHANNEL_BINDINGS, &in,
118 &mech, &out, NULL, NULL);
119 if (in.value)
120 free(in.value);
121
122 if (major == GSS_S_COMPLETE)
123 auth_state->state = GSSAPI_COMPLETE;
124 else if (major == GSS_S_CONTINUE_NEEDED)
125 auth_state->state = GSSAPI_CONTINUE;
126 else {
127 vpn_progress(vpninfo, PRG_ERR,
128 _("Error generating GSSAPI response:\n"));
129 print_gss_err(vpninfo, "gss_init_sec_context()", mech, major, minor);
130 fail_gssapi:
131 auth_state->state = AUTH_FAILED;
132 cleanup_gssapi_auth(vpninfo, auth_state);
133 /* If we were *trying*, then -EAGAIN. Else -ENOENT to let another
134 auth method try without having to reconnect first. */
135 return in.value ? -EAGAIN : -ENOENT;
136 }
137 buf_append(hdrbuf, "%sAuthorization: Negotiate ", proxy ? "Proxy-" : "");
138 buf_append_base64(hdrbuf, out.value, out.length);
139 buf_append(hdrbuf, "\r\n");
140
141 gss_release_buffer(&minor, &out);
142 if (!auth_state->challenge) {
143 if (proxy)
144 vpn_progress(vpninfo, PRG_INFO,
145 _("Attempting GSSAPI authentication to proxy\n"));
146 else
147 vpn_progress(vpninfo, PRG_INFO,
148 _("Attempting GSSAPI authentication to server '%s'\n"),
149 vpninfo->hostname);
150 }
151
152 return 0;
153 }
154
155 /* auth_state is NULL when called from socks_gssapi_auth() */
cleanup_gssapi_auth(struct openconnect_info * vpninfo,struct http_auth_state * auth_state)156 void cleanup_gssapi_auth(struct openconnect_info *vpninfo,
157 struct http_auth_state *auth_state)
158 {
159 OM_uint32 minor;
160
161 if (auth_state->gss_target_name != GSS_C_NO_NAME)
162 gss_release_name(&minor, &auth_state->gss_target_name);
163
164 if (auth_state->gss_context != GSS_C_NO_CONTEXT)
165 gss_delete_sec_context(&minor, &auth_state->gss_context, GSS_C_NO_BUFFER);
166
167 /* Shouldn't be necessary, but make sure... */
168 auth_state->gss_target_name = GSS_C_NO_NAME;
169 auth_state->gss_context = GSS_C_NO_CONTEXT;
170 }
171
socks_gssapi_auth(struct openconnect_info * vpninfo)172 int socks_gssapi_auth(struct openconnect_info *vpninfo)
173 {
174 gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
175 gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
176 gss_OID mech = GSS_C_NO_OID;
177 OM_uint32 major, minor;
178 unsigned char *pktbuf;
179 int i;
180 int ret = -EIO;
181 struct http_auth_state *auth_state = &vpninfo->proxy_auth[AUTH_TYPE_GSSAPI];
182
183 if (gssapi_setup(vpninfo, auth_state, "rcmd", 1))
184 return -EIO;
185
186 pktbuf = malloc(65538);
187 if (!pktbuf)
188 return -ENOMEM;
189 while (1) {
190 major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &auth_state->gss_context,
191 auth_state->gss_target_name, (gss_OID)&gss_mech_spnego,
192 GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_DELEG_FLAG | GSS_C_SEQUENCE_FLAG,
193 GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &in, &mech,
194 &out, NULL, NULL);
195 in.value = NULL;
196 if (major == GSS_S_COMPLETE) {
197 /* If we still have a token to send, send it. */
198 if (!out.length) {
199 vpn_progress(vpninfo, PRG_DEBUG,
200 _("GSSAPI authentication completed\n"));
201 gss_release_buffer(&minor, &out);
202 ret = 0;
203 break;
204 }
205 } else if (major != GSS_S_CONTINUE_NEEDED) {
206 print_gss_err(vpninfo, "gss_init_sec_context()", mech, major, minor);
207 break;
208 }
209 if (out.length > 65535) {
210 vpn_progress(vpninfo, PRG_ERR,
211 _("GSSAPI token too large (%zd bytes)\n"),
212 out.length);
213 break;
214 }
215
216 pktbuf[0] = 1; /* ver */
217 pktbuf[1] = 1; /* mtyp */
218 store_be16(pktbuf + 2, out.length);
219 memcpy(pktbuf + 4, out.value, out.length);
220
221 free(out.value);
222
223 vpn_progress(vpninfo, PRG_TRACE,
224 _("Sending GSSAPI token of %zu bytes\n"), out.length + 4);
225
226 i = vpninfo->ssl_write(vpninfo, (void *)pktbuf, out.length + 4);
227 if (i < 0) {
228 vpn_progress(vpninfo, PRG_ERR,
229 _("Failed to send GSSAPI authentication token to proxy: %s\n"),
230 strerror(-i));
231 break;
232 }
233
234 i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, 4);
235 if (i < 0) {
236 vpn_progress(vpninfo, PRG_ERR,
237 _("Failed to receive GSSAPI authentication token from proxy: %s\n"),
238 strerror(-i));
239 break;
240 }
241 if (pktbuf[1] == 0xff) {
242 vpn_progress(vpninfo, PRG_ERR,
243 _("SOCKS server reported GSSAPI context failure\n"));
244 break;
245 } else if (pktbuf[1] != 1) {
246 vpn_progress(vpninfo, PRG_ERR,
247 _("Unknown GSSAPI status response (0x%02x) from SOCKS server\n"),
248 pktbuf[1]);
249 break;
250 }
251 in.length = load_be16(pktbuf + 2);
252 in.value = pktbuf;
253
254 if (!in.length) {
255 vpn_progress(vpninfo, PRG_DEBUG,
256 _("GSSAPI authentication completed\n"));
257 ret = 0;
258 break;
259 }
260
261 i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, in.length);
262 if (i < 0) {
263 vpn_progress(vpninfo, PRG_ERR,
264 _("Failed to receive GSSAPI authentication token from proxy: %s\n"),
265 strerror(-i));
266 break;
267 }
268 vpn_progress(vpninfo, PRG_TRACE, _("Got GSSAPI token of %zu bytes: %02x %02x %02x %02x\n"),
269 in.length, pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3]);
270 }
271
272 if (!ret) {
273 ret = -EIO;
274
275 pktbuf[0] = 0;
276 in.value = pktbuf;
277 in.length = 1;
278
279 major = gss_wrap(&minor, auth_state->gss_context, 0,
280 GSS_C_QOP_DEFAULT, &in, NULL, &out);
281 if (major != GSS_S_COMPLETE) {
282 print_gss_err(vpninfo, "gss_wrap()", mech, major, minor);
283 goto err;
284 }
285
286 pktbuf[0] = 1;
287 pktbuf[1] = 2;
288 store_be16(pktbuf + 2, out.length);
289 memcpy(pktbuf + 4, out.value, out.length);
290
291 free(out.value);
292
293 vpn_progress(vpninfo, PRG_TRACE,
294 _("Sending GSSAPI protection negotiation of %zu bytes\n"), out.length + 4);
295
296 i = vpninfo->ssl_write(vpninfo, (void *)pktbuf, out.length + 4);
297 if (i < 0) {
298 vpn_progress(vpninfo, PRG_ERR,
299 _("Failed to send GSSAPI protection response to proxy: %s\n"),
300 strerror(-i));
301 goto err;
302 }
303
304 i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, 4);
305 if (i < 0) {
306 vpn_progress(vpninfo, PRG_ERR,
307 _("Failed to receive GSSAPI protection response from proxy: %s\n"),
308 strerror(-i));
309 goto err;
310 }
311 in.length = load_be16(pktbuf + 2);
312 in.value = pktbuf;
313
314 i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, in.length);
315 if (i < 0) {
316 vpn_progress(vpninfo, PRG_ERR,
317 _("Failed to receive GSSAPI protection response from proxy: %s\n"),
318 strerror(-i));
319 goto err;
320 }
321 vpn_progress(vpninfo, PRG_TRACE,
322 _("Got GSSAPI protection response of %zu bytes: %02x %02x %02x %02x\n"),
323 in.length, pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3]);
324
325 major = gss_unwrap(&minor, auth_state->gss_context, &in, &out, NULL, GSS_C_QOP_DEFAULT);
326 if (major != GSS_S_COMPLETE) {
327 print_gss_err(vpninfo, "gss_unwrap()", mech, major, minor);
328 goto err;
329 }
330 if (out.length != 1) {
331 vpn_progress(vpninfo, PRG_ERR,
332 _("Invalid GSSAPI protection response from proxy (%zu bytes)\n"),
333 out.length);
334 gss_release_buffer(&minor, &out);
335 goto err;
336 }
337 i = *(char *)out.value;
338 gss_release_buffer(&minor, &out);
339 if (i == 1) {
340 vpn_progress(vpninfo, PRG_ERR,
341 _("SOCKS proxy demands message integrity, which is not supported\n"));
342 goto err;
343 } else if (i == 2) {
344 vpn_progress(vpninfo, PRG_ERR,
345 _("SOCKS proxy demands message confidentiality, which is not supported\n"));
346 goto err;
347 } else if (i) {
348 vpn_progress(vpninfo, PRG_ERR,
349 _("SOCKS proxy demands protection unknown type 0x%02x\n"),
350 (unsigned char)i);
351 goto err;
352 }
353 ret = 0;
354 }
355 err:
356 cleanup_gssapi_auth(vpninfo, NULL);
357 free(pktbuf);
358
359 return ret;
360 }
361