1/* server.c --- DIGEST-MD5 mechanism from RFC 2831, server side.
2 * Copyright (C) 2002-2012 Simon Josefsson
3 *
4 * This file is part of GNU SASL Library.
5 *
6 * GNU SASL Library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * GNU SASL Library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with GNU SASL Library; if not, write to the Free
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27/* Get specification. */
28#include "digest-md5.h"
29
30/* Get malloc, free. */
31#include <stdlib.h>
32
33/* Get memcpy, strdup, strlen. */
34#include <string.h>
35
36/* Get tools. */
37#include "nonascii.h"
38#include "tokens.h"
39#include "parser.h"
40#include "printer.h"
41#include "free.h"
42#include "session.h"
43#include "digesthmac.h"
44#include "validate.h"
45#include "qop.h"
46
47#define NONCE_ENTROPY_BYTES 16
48
49struct _Gsasl_digest_md5_server_state
50{
51 int step;
52 unsigned long readseqnum, sendseqnum;
53 char secret[DIGEST_MD5_LENGTH];
54 char kic[DIGEST_MD5_LENGTH];
55 char kcc[DIGEST_MD5_LENGTH];
56 char kis[DIGEST_MD5_LENGTH];
57 char kcs[DIGEST_MD5_LENGTH];
58 digest_md5_challenge challenge;
59 digest_md5_response response;
60 digest_md5_finish finish;
61};
62typedef struct _Gsasl_digest_md5_server_state _Gsasl_digest_md5_server_state;
63
64int
65_gsasl_digest_md5_server_start (Gsasl_session * sctx, void **mech_data)
66{
67 _Gsasl_digest_md5_server_state *state;
68 char nonce[NONCE_ENTROPY_BYTES];
69 char *p;
70 int rc;
71
72 rc = gsasl_nonce (nonce, NONCE_ENTROPY_BYTES);
73 if (rc != GSASL_OK)
74 return rc;
75
76 rc = gsasl_base64_to (nonce, NONCE_ENTROPY_BYTES, &p, NULL);
77 if (rc != GSASL_OK)
78 return rc;
79
80 state = calloc (1, sizeof (*state));
81 if (state == NULL)
82 {
83 free (p);
84 return GSASL_MALLOC_ERROR;
85 }
86
87 state->challenge.qops = DIGEST_MD5_QOP_AUTH;
88 state->challenge.ciphers = 0;
89
90 state->challenge.nonce = p;
91 state->challenge.utf8 = 1;
92
93 *mech_data = state;
94
95 return GSASL_OK;
96}
97
98static char
99_gsasl_digest_md5_hexdigit_to_char (char hexdigit)
100{
101 /* The hex representation always contains lowercase alphabetic
102 characters. See RFC 2831, 1.1. */
103
104 if (hexdigit >= '0' && hexdigit <= '9')
105 return hexdigit - '0';
106 if (hexdigit >= 'a' && hexdigit <= 'z')
107 return hexdigit - 'a' + 10;
108
109 return -1;
110}
111
112static char
113_gsasl_digest_md5_hex_to_char (char u, char l)
114{
115 return (char) (((unsigned char) _gsasl_digest_md5_hexdigit_to_char (u)) *
116 16 + _gsasl_digest_md5_hexdigit_to_char (l));
117}
118
119static int
120_gsasl_digest_md5_set_hashed_secret (char *secret, const char *hex_secret)
121{
122 /* Convert the hex string containing the secret to a byte array */
123 const char *p;
124 char *s;
125
126 if (!hex_secret)
127 return GSASL_AUTHENTICATION_ERROR;
128
129 s = secret;
130 p = hex_secret;
131 while (*p)
132 {
133 *s = _gsasl_digest_md5_hex_to_char (p[0], p[1]);
134 s++;
135
136 p += 2;
137 }
138
139 return GSASL_OK;
140}
141
142int
143_gsasl_digest_md5_server_step (Gsasl_session * sctx,
144 void *mech_data,
145 const char *input,
146 size_t input_len,
147 char **output, size_t * output_len)
148{
149 _Gsasl_digest_md5_server_state *state = mech_data;
150 int rc, res;
151
152 *output = NULL;
153 *output_len = 0;
154
155 switch (state->step)
156 {
157 case 0:
158 /* Set realm. */
159 {
160 const char *c;
161 c = gsasl_property_get (sctx, GSASL_REALM);
162 if (c)
163 {
164 state->challenge.nrealms = 1;
165
166 state->challenge.realms =
167 malloc (sizeof (*state->challenge.realms));
168 if (!state->challenge.realms)
169 return GSASL_MALLOC_ERROR;
170
171 state->challenge.realms[0] = strdup (c);
172 if (!state->challenge.realms[0])
173 return GSASL_MALLOC_ERROR;
174 }
175 }
176
177 /* Set QOP */
178 {
179 const char *qopstr = gsasl_property_get (sctx, GSASL_QOPS);
180
181 if (qopstr)
182 {
183 int qops = digest_md5_qopstr2qops (qopstr);
184
185 if (qops == -1)
186 return GSASL_MALLOC_ERROR;
187
188 /* We don't support confidentiality right now. */
189 if (qops & DIGEST_MD5_QOP_AUTH_CONF)
190 return GSASL_AUTHENTICATION_ERROR;
191
192 if (qops)
193 state->challenge.qops = qops;
194 }
195 }
196
197 /* FIXME: cipher, maxbuf, more realms. */
198
199 /* Create challenge. */
200 *output = digest_md5_print_challenge (&state->challenge);
201 if (!*output)
202 return GSASL_AUTHENTICATION_ERROR;
203
204 *output_len = strlen (*output);
205 state->step++;
206 res = GSASL_NEEDS_MORE;
207 break;
208
209 case 1:
210 if (digest_md5_parse_response (input, input_len, &state->response) < 0)
211 return GSASL_MECHANISM_PARSE_ERROR;
212
213 /* Make sure response is consistent with challenge. */
214 if (digest_md5_validate (&state->challenge, &state->response) < 0)
215 return GSASL_MECHANISM_PARSE_ERROR;
216
217 /* Store properties, from the client response. */
218 if (state->response.utf8)
219 {
220 gsasl_property_set (sctx, GSASL_AUTHID, state->response.username);
221 gsasl_property_set (sctx, GSASL_REALM, state->response.realm);
222 }
223 else
224 {
225 /* Client provided username/realm in ISO-8859-1 form,
226 convert it to UTF-8 since the library is all-UTF-8. */
227 char *tmp;
228
229 tmp = latin1toutf8 (state->response.username);
230 if (!tmp)
231 return GSASL_MALLOC_ERROR;
232 gsasl_property_set (sctx, GSASL_AUTHID, tmp);
233 free (tmp);
234
235 tmp = latin1toutf8 (state->response.realm);
236 if (!tmp)
237 return GSASL_MALLOC_ERROR;
238 gsasl_property_set (sctx, GSASL_REALM, tmp);
239 free (tmp);
240 }
241 gsasl_property_set (sctx, GSASL_AUTHZID, state->response.authzid);
242
243 /* FIXME: cipher, maxbuf. */
244
245 /* Compute secret. */
246 {
247 const char *passwd;
248 const char *hashed_passwd;
249
250 hashed_passwd =
251 gsasl_property_get (sctx, GSASL_DIGEST_MD5_HASHED_PASSWORD);
252 if (hashed_passwd)
253 {
254 if (strlen (hashed_passwd) != (DIGEST_MD5_LENGTH * 2))
255 return GSASL_AUTHENTICATION_ERROR;
256
257 rc = _gsasl_digest_md5_set_hashed_secret (state->secret,
258 hashed_passwd);
259 if (rc != GSASL_OK)
260 return rc;
261 }
262 else if ((passwd = gsasl_property_get (sctx, GSASL_PASSWORD)) != NULL)
263 {
264 char *tmp, *tmp2;
265
266 tmp2 = utf8tolatin1ifpossible (passwd);
267
268 rc = asprintf (&tmp, "%s:%s:%s", state->response.username,
269 state->response.realm ?
270 state->response.realm : "", tmp2);
271 free (tmp2);
272 if (rc < 0)
273 return GSASL_MALLOC_ERROR;
274
275 rc = gsasl_md5 (tmp, strlen (tmp), &tmp2);
276 free (tmp);
277 if (rc != GSASL_OK)
278 return rc;
279
280 memcpy (state->secret, tmp2, DIGEST_MD5_LENGTH);
281 free (tmp2);
282 }
283 else
284 {
285 return GSASL_NO_PASSWORD;
286 }
287 }
288
289 /* Check client response. */
290 {
291 char check[DIGEST_MD5_RESPONSE_LENGTH + 1];
292
293 rc = digest_md5_hmac (check, state->secret,
294 state->response.nonce, state->response.nc,
295 state->response.cnonce, state->response.qop,
296 state->response.authzid,
297 state->response.digesturi, 0,
298 state->response.cipher,
299 state->kic, state->kis, state->kcc, state->kcs);
300 if (rc)
301 return GSASL_AUTHENTICATION_ERROR;
302
303 if (strcmp (state->response.response, check) != 0)
304 return GSASL_AUTHENTICATION_ERROR;
305 }
306
307 /* Create finish token. */
308 rc = digest_md5_hmac (state->finish.rspauth, state->secret,
309 state->response.nonce, state->response.nc,
310 state->response.cnonce, state->response.qop,
311 state->response.authzid,
312 state->response.digesturi, 1,
313 state->response.cipher, NULL, NULL, NULL, NULL);
314 if (rc)
315 return GSASL_AUTHENTICATION_ERROR;
316
317 *output = digest_md5_print_finish (&state->finish);
318 if (!*output)
319 return GSASL_MALLOC_ERROR;
320
321 *output_len = strlen (*output);
322
323 state->step++;
324 res = GSASL_OK;
325 break;
326
327 default:
328 res = GSASL_MECHANISM_CALLED_TOO_MANY_TIMES;
329 break;
330 }
331
332 return res;
333}
334
335void
336_gsasl_digest_md5_server_finish (Gsasl_session * sctx, void *mech_data)
337{
338 _Gsasl_digest_md5_server_state *state = mech_data;
339
340 if (!state)
341 return;
342
343 digest_md5_free_challenge (&state->challenge);
344 digest_md5_free_response (&state->response);
345 digest_md5_free_finish (&state->finish);
346
347 free (state);
348}
349
350int
351_gsasl_digest_md5_server_encode (Gsasl_session * sctx,
352 void *mech_data,
353 const char *input,
354 size_t input_len,
355 char **output, size_t * output_len)
356{
357 _Gsasl_digest_md5_server_state *state = mech_data;
358 int res;
359
360 res = digest_md5_encode (input, input_len, output, output_len,
361 state->response.qop, state->sendseqnum,
362 state->kis);
363 if (res)
364 return res == -2 ? GSASL_NEEDS_MORE : GSASL_INTEGRITY_ERROR;
365
366 if (state->sendseqnum == 4294967295UL)
367 state->sendseqnum = 0;
368 else
369 state->sendseqnum++;
370
371 return GSASL_OK;
372}
373
374int
375_gsasl_digest_md5_server_decode (Gsasl_session * sctx,
376 void *mech_data,
377 const char *input,
378 size_t input_len,
379 char **output, size_t * output_len)
380{
381 _Gsasl_digest_md5_server_state *state = mech_data;
382 int res;
383
384 res = digest_md5_decode (input, input_len, output, output_len,
385 state->response.qop, state->readseqnum,
386 state->kic);
387 if (res)
388 return res == -2 ? GSASL_NEEDS_MORE : GSASL_INTEGRITY_ERROR;
389
390 if (state->readseqnum == 4294967295UL)
391 state->readseqnum = 0;
392 else
393 state->readseqnum++;
394
395 return GSASL_OK;
396}
397