1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | * RFC4178 Simple and Protected GSS-API Negotiation Mechanism |
22 | * |
23 | ***************************************************************************/ |
24 | |
25 | #include "curl_setup.h" |
26 | |
27 | #if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO) |
28 | |
29 | #include <curl/curl.h> |
30 | |
31 | #include "vauth/vauth.h" |
32 | #include "urldata.h" |
33 | #include "curl_base64.h" |
34 | #include "warnless.h" |
35 | #include "curl_multibyte.h" |
36 | #include "sendf.h" |
37 | #include "strerror.h" |
38 | |
39 | /* The last #include files should be: */ |
40 | #include "curl_memory.h" |
41 | #include "memdebug.h" |
42 | |
43 | /* |
44 | * Curl_auth_is_spnego_supported() |
45 | * |
46 | * This is used to evaluate if SPNEGO (Negotiate) is supported. |
47 | * |
48 | * Parameters: None |
49 | * |
50 | * Returns TRUE if Negotiate is supported by Windows SSPI. |
51 | */ |
52 | bool Curl_auth_is_spnego_supported(void) |
53 | { |
54 | PSecPkgInfo SecurityPackage; |
55 | SECURITY_STATUS status; |
56 | |
57 | /* Query the security package for Negotiate */ |
58 | status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) |
59 | TEXT(SP_NAME_NEGOTIATE), |
60 | &SecurityPackage); |
61 | |
62 | /* Release the package buffer as it is not required anymore */ |
63 | if(status == SEC_E_OK) { |
64 | s_pSecFn->FreeContextBuffer(SecurityPackage); |
65 | } |
66 | |
67 | |
68 | return (status == SEC_E_OK ? TRUE : FALSE); |
69 | } |
70 | |
71 | /* |
72 | * Curl_auth_decode_spnego_message() |
73 | * |
74 | * This is used to decode an already encoded SPNEGO (Negotiate) challenge |
75 | * message. |
76 | * |
77 | * Parameters: |
78 | * |
79 | * data [in] - The session handle. |
80 | * user [in] - The user name in the format User or Domain\User. |
81 | * password [in] - The user's password. |
82 | * service [in] - The service type such as http, smtp, pop or imap. |
83 | * host [in] - The host name. |
84 | * chlg64 [in] - The optional base64 encoded challenge message. |
85 | * nego [in/out] - The Negotiate data struct being used and modified. |
86 | * |
87 | * Returns CURLE_OK on success. |
88 | */ |
89 | CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, |
90 | const char *user, |
91 | const char *password, |
92 | const char *service, |
93 | const char *host, |
94 | const char *chlg64, |
95 | struct negotiatedata *nego) |
96 | { |
97 | CURLcode result = CURLE_OK; |
98 | size_t chlglen = 0; |
99 | unsigned char *chlg = NULL; |
100 | PSecPkgInfo SecurityPackage; |
101 | SecBuffer chlg_buf[2]; |
102 | SecBuffer resp_buf; |
103 | SecBufferDesc chlg_desc; |
104 | SecBufferDesc resp_desc; |
105 | unsigned long attrs; |
106 | TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ |
107 | |
108 | #if defined(CURL_DISABLE_VERBOSE_STRINGS) |
109 | (void) data; |
110 | #endif |
111 | |
112 | if(nego->context && nego->status == SEC_E_OK) { |
113 | /* We finished successfully our part of authentication, but server |
114 | * rejected it (since we're again here). Exit with an error since we |
115 | * can't invent anything better */ |
116 | Curl_auth_cleanup_spnego(nego); |
117 | return CURLE_LOGIN_DENIED; |
118 | } |
119 | |
120 | if(!nego->spn) { |
121 | /* Generate our SPN */ |
122 | nego->spn = Curl_auth_build_spn(service, host, NULL); |
123 | if(!nego->spn) |
124 | return CURLE_OUT_OF_MEMORY; |
125 | } |
126 | |
127 | if(!nego->output_token) { |
128 | /* Query the security package for Negotiate */ |
129 | nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) |
130 | TEXT(SP_NAME_NEGOTIATE), |
131 | &SecurityPackage); |
132 | if(nego->status != SEC_E_OK) { |
133 | failf(data, "SSPI: couldn't get auth info" ); |
134 | return CURLE_AUTH_ERROR; |
135 | } |
136 | |
137 | nego->token_max = SecurityPackage->cbMaxToken; |
138 | |
139 | /* Release the package buffer as it is not required anymore */ |
140 | s_pSecFn->FreeContextBuffer(SecurityPackage); |
141 | |
142 | /* Allocate our output buffer */ |
143 | nego->output_token = malloc(nego->token_max); |
144 | if(!nego->output_token) |
145 | return CURLE_OUT_OF_MEMORY; |
146 | } |
147 | |
148 | if(!nego->credentials) { |
149 | /* Do we have credentials to use or are we using single sign-on? */ |
150 | if(user && *user) { |
151 | /* Populate our identity structure */ |
152 | result = Curl_create_sspi_identity(user, password, &nego->identity); |
153 | if(result) |
154 | return result; |
155 | |
156 | /* Allow proper cleanup of the identity structure */ |
157 | nego->p_identity = &nego->identity; |
158 | } |
159 | else |
160 | /* Use the current Windows user */ |
161 | nego->p_identity = NULL; |
162 | |
163 | /* Allocate our credentials handle */ |
164 | nego->credentials = calloc(1, sizeof(CredHandle)); |
165 | if(!nego->credentials) |
166 | return CURLE_OUT_OF_MEMORY; |
167 | |
168 | /* Acquire our credentials handle */ |
169 | nego->status = |
170 | s_pSecFn->AcquireCredentialsHandle(NULL, |
171 | (TCHAR *)TEXT(SP_NAME_NEGOTIATE), |
172 | SECPKG_CRED_OUTBOUND, NULL, |
173 | nego->p_identity, NULL, NULL, |
174 | nego->credentials, &expiry); |
175 | if(nego->status != SEC_E_OK) |
176 | return CURLE_AUTH_ERROR; |
177 | |
178 | /* Allocate our new context handle */ |
179 | nego->context = calloc(1, sizeof(CtxtHandle)); |
180 | if(!nego->context) |
181 | return CURLE_OUT_OF_MEMORY; |
182 | } |
183 | |
184 | if(chlg64 && *chlg64) { |
185 | /* Decode the base-64 encoded challenge message */ |
186 | if(*chlg64 != '=') { |
187 | result = Curl_base64_decode(chlg64, &chlg, &chlglen); |
188 | if(result) |
189 | return result; |
190 | } |
191 | |
192 | /* Ensure we have a valid challenge message */ |
193 | if(!chlg) { |
194 | infof(data, "SPNEGO handshake failure (empty challenge message)" ); |
195 | return CURLE_BAD_CONTENT_ENCODING; |
196 | } |
197 | |
198 | /* Setup the challenge "input" security buffer */ |
199 | chlg_desc.ulVersion = SECBUFFER_VERSION; |
200 | chlg_desc.cBuffers = 1; |
201 | chlg_desc.pBuffers = &chlg_buf[0]; |
202 | chlg_buf[0].BufferType = SECBUFFER_TOKEN; |
203 | chlg_buf[0].pvBuffer = chlg; |
204 | chlg_buf[0].cbBuffer = curlx_uztoul(chlglen); |
205 | |
206 | #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS |
207 | /* ssl context comes from Schannel. |
208 | * When extended protection is used in IIS server, |
209 | * we have to pass a second SecBuffer to the SecBufferDesc |
210 | * otherwise IIS will not pass the authentication (401 response). |
211 | * Minimum supported version is Windows 7. |
212 | * https://docs.microsoft.com/en-us/security-updates |
213 | * /SecurityAdvisories/2009/973811 |
214 | */ |
215 | if(nego->sslContext) { |
216 | SEC_CHANNEL_BINDINGS channelBindings; |
217 | SecPkgContext_Bindings pkgBindings; |
218 | pkgBindings.Bindings = &channelBindings; |
219 | nego->status = s_pSecFn->QueryContextAttributes( |
220 | nego->sslContext, |
221 | SECPKG_ATTR_ENDPOINT_BINDINGS, |
222 | &pkgBindings |
223 | ); |
224 | if(nego->status == SEC_E_OK) { |
225 | chlg_desc.cBuffers++; |
226 | chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; |
227 | chlg_buf[1].cbBuffer = pkgBindings.BindingsLength; |
228 | chlg_buf[1].pvBuffer = pkgBindings.Bindings; |
229 | } |
230 | } |
231 | #endif |
232 | } |
233 | |
234 | /* Setup the response "output" security buffer */ |
235 | resp_desc.ulVersion = SECBUFFER_VERSION; |
236 | resp_desc.cBuffers = 1; |
237 | resp_desc.pBuffers = &resp_buf; |
238 | resp_buf.BufferType = SECBUFFER_TOKEN; |
239 | resp_buf.pvBuffer = nego->output_token; |
240 | resp_buf.cbBuffer = curlx_uztoul(nego->token_max); |
241 | |
242 | /* Generate our challenge-response message */ |
243 | nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials, |
244 | chlg ? nego->context : |
245 | NULL, |
246 | nego->spn, |
247 | ISC_REQ_CONFIDENTIALITY, |
248 | 0, SECURITY_NATIVE_DREP, |
249 | chlg ? &chlg_desc : NULL, |
250 | 0, nego->context, |
251 | &resp_desc, &attrs, |
252 | &expiry); |
253 | |
254 | /* Free the decoded challenge as it is not required anymore */ |
255 | free(chlg); |
256 | |
257 | if(GSS_ERROR(nego->status)) { |
258 | char buffer[STRERROR_LEN]; |
259 | failf(data, "InitializeSecurityContext failed: %s" , |
260 | Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); |
261 | |
262 | if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) |
263 | return CURLE_OUT_OF_MEMORY; |
264 | |
265 | return CURLE_AUTH_ERROR; |
266 | } |
267 | |
268 | if(nego->status == SEC_I_COMPLETE_NEEDED || |
269 | nego->status == SEC_I_COMPLETE_AND_CONTINUE) { |
270 | nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc); |
271 | if(GSS_ERROR(nego->status)) { |
272 | char buffer[STRERROR_LEN]; |
273 | failf(data, "CompleteAuthToken failed: %s" , |
274 | Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); |
275 | |
276 | if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) |
277 | return CURLE_OUT_OF_MEMORY; |
278 | |
279 | return CURLE_AUTH_ERROR; |
280 | } |
281 | } |
282 | |
283 | nego->output_token_length = resp_buf.cbBuffer; |
284 | |
285 | return result; |
286 | } |
287 | |
288 | /* |
289 | * Curl_auth_create_spnego_message() |
290 | * |
291 | * This is used to generate an already encoded SPNEGO (Negotiate) response |
292 | * message ready for sending to the recipient. |
293 | * |
294 | * Parameters: |
295 | * |
296 | * data [in] - The session handle. |
297 | * nego [in/out] - The Negotiate data struct being used and modified. |
298 | * outptr [in/out] - The address where a pointer to newly allocated memory |
299 | * holding the result will be stored upon completion. |
300 | * outlen [out] - The length of the output message. |
301 | * |
302 | * Returns CURLE_OK on success. |
303 | */ |
304 | CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, |
305 | struct negotiatedata *nego, |
306 | char **outptr, size_t *outlen) |
307 | { |
308 | CURLcode result; |
309 | |
310 | /* Base64 encode the already generated response */ |
311 | result = Curl_base64_encode(data, |
312 | (const char *) nego->output_token, |
313 | nego->output_token_length, |
314 | outptr, outlen); |
315 | |
316 | if(result) |
317 | return result; |
318 | |
319 | if(!*outptr || !*outlen) { |
320 | free(*outptr); |
321 | return CURLE_REMOTE_ACCESS_DENIED; |
322 | } |
323 | |
324 | return CURLE_OK; |
325 | } |
326 | |
327 | /* |
328 | * Curl_auth_cleanup_spnego() |
329 | * |
330 | * This is used to clean up the SPNEGO (Negotiate) specific data. |
331 | * |
332 | * Parameters: |
333 | * |
334 | * nego [in/out] - The Negotiate data struct being cleaned up. |
335 | * |
336 | */ |
337 | void Curl_auth_cleanup_spnego(struct negotiatedata *nego) |
338 | { |
339 | /* Free our security context */ |
340 | if(nego->context) { |
341 | s_pSecFn->DeleteSecurityContext(nego->context); |
342 | free(nego->context); |
343 | nego->context = NULL; |
344 | } |
345 | |
346 | /* Free our credentials handle */ |
347 | if(nego->credentials) { |
348 | s_pSecFn->FreeCredentialsHandle(nego->credentials); |
349 | free(nego->credentials); |
350 | nego->credentials = NULL; |
351 | } |
352 | |
353 | /* Free our identity */ |
354 | Curl_sspi_free_identity(nego->p_identity); |
355 | nego->p_identity = NULL; |
356 | |
357 | /* Free the SPN and output token */ |
358 | Curl_safefree(nego->spn); |
359 | Curl_safefree(nego->output_token); |
360 | |
361 | /* Reset any variables */ |
362 | nego->status = 0; |
363 | nego->token_max = 0; |
364 | nego->noauthpersist = FALSE; |
365 | nego->havenoauthpersist = FALSE; |
366 | nego->havenegdata = FALSE; |
367 | nego->havemultiplerequests = FALSE; |
368 | } |
369 | |
370 | #endif /* USE_WINDOWS_SSPI && USE_SPNEGO */ |
371 | |