1/* client.c --- DIGEST-MD5 mechanism from RFC 2831, client 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, 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 "qop.h"
45
46#define CNONCE_ENTROPY_BYTES 16
47
48struct _Gsasl_digest_md5_client_state
49{
50 int step;
51 unsigned long readseqnum, sendseqnum;
52 char secret[DIGEST_MD5_LENGTH];
53 char kic[DIGEST_MD5_LENGTH];
54 char kcc[DIGEST_MD5_LENGTH];
55 char kis[DIGEST_MD5_LENGTH];
56 char kcs[DIGEST_MD5_LENGTH];
57 digest_md5_challenge challenge;
58 digest_md5_response response;
59 digest_md5_finish finish;
60};
61typedef struct _Gsasl_digest_md5_client_state _Gsasl_digest_md5_client_state;
62
63int
64_gsasl_digest_md5_client_start (Gsasl_session * sctx, void **mech_data)
65{
66 _Gsasl_digest_md5_client_state *state;
67 char nonce[CNONCE_ENTROPY_BYTES];
68 char *p;
69 int rc;
70
71 rc = gsasl_nonce (nonce, CNONCE_ENTROPY_BYTES);
72 if (rc != GSASL_OK)
73 return rc;
74
75 rc = gsasl_base64_to (nonce, CNONCE_ENTROPY_BYTES, &p, NULL);
76 if (rc != GSASL_OK)
77 return rc;
78
79 state = calloc (1, sizeof (*state));
80 if (state == NULL)
81 {
82 free (p);
83 return GSASL_MALLOC_ERROR;
84 }
85
86 state->response.cnonce = p;
87 state->response.nc = 1;
88
89 *mech_data = state;
90
91 return GSASL_OK;
92}
93
94int
95_gsasl_digest_md5_client_step (Gsasl_session * sctx,
96 void *mech_data,
97 const char *input,
98 size_t input_len,
99 char **output, size_t * output_len)
100{
101 _Gsasl_digest_md5_client_state *state = mech_data;
102 int rc, res;
103
104 *output = NULL;
105 *output_len = 0;
106
107 switch (state->step)
108 {
109 case 0:
110 state->step++;
111 if (input_len == 0)
112 return GSASL_NEEDS_MORE;
113 /* fall through */
114
115 case 1:
116 {
117 if (digest_md5_parse_challenge (input, input_len,
118 &state->challenge) < 0)
119 return GSASL_MECHANISM_PARSE_ERROR;
120
121 /* FIXME: How to let application know of remaining realms?
122 One idea, add a GSASL_REALM_COUNT property, and have the
123 GSASL_REALM be that many concatenated zero terminated realm
124 strings. Slightly hackish, though. Another cleaner
125 approach would be to add gsasl_property_set_array and
126 gsasl_property_get_array APIs, for those properties that
127 may be used multiple times. */
128 if (state->challenge.nrealms > 0)
129 gsasl_property_set (sctx, GSASL_REALM, state->challenge.realms[0]);
130 else
131 gsasl_property_set (sctx, GSASL_REALM, NULL);
132
133 /* FIXME: cipher, maxbuf. */
134
135 /* Create response token. */
136 state->response.utf8 = 1;
137
138 gsasl_property_set (sctx, GSASL_QOPS,
139 digest_md5_qops2qopstr (state->challenge.qops));
140
141 {
142 const char *qop = gsasl_property_get (sctx, GSASL_QOP);
143
144 if (!qop)
145 state->response.qop = GSASL_QOP_AUTH;
146 else if (strcmp (qop, "qop-int") == 0)
147 state->response.qop = GSASL_QOP_AUTH_INT;
148 else if (strcmp (qop, "qop-auth") == 0)
149 state->response.qop = GSASL_QOP_AUTH;
150 else
151 /* We don't support confidentiality or unknown
152 keywords. */
153 return GSASL_AUTHENTICATION_ERROR;
154 }
155
156 state->response.nonce = strdup (state->challenge.nonce);
157 if (!state->response.nonce)
158 return GSASL_MALLOC_ERROR;
159
160 {
161 const char *service = gsasl_property_get (sctx, GSASL_SERVICE);
162 const char *hostname = gsasl_property_get (sctx, GSASL_HOSTNAME);
163 if (!service)
164 return GSASL_NO_SERVICE;
165 if (!hostname)
166 return GSASL_NO_HOSTNAME;
167 if (asprintf (&state->response.digesturi, "%s/%s",
168 service, hostname) < 0)
169 return GSASL_MALLOC_ERROR;
170 }
171
172 {
173 const char *c;
174 char *tmp, *tmp2;
175
176 c = gsasl_property_get (sctx, GSASL_AUTHID);
177 if (!c)
178 return GSASL_NO_AUTHID;
179
180 state->response.username = strdup (c);
181 if (!state->response.username)
182 return GSASL_MALLOC_ERROR;
183
184 c = gsasl_property_get (sctx, GSASL_AUTHZID);
185 if (c)
186 {
187 state->response.authzid = strdup (c);
188 if (!state->response.authzid)
189 return GSASL_MALLOC_ERROR;
190 }
191
192 gsasl_callback (NULL, sctx, GSASL_REALM);
193 c = gsasl_property_fast (sctx, GSASL_REALM);
194 if (c)
195 {
196 state->response.realm = strdup (c);
197 if (!state->response.realm)
198 return GSASL_MALLOC_ERROR;
199 }
200
201 c = gsasl_property_get (sctx, GSASL_PASSWORD);
202 if (!c)
203 return GSASL_NO_PASSWORD;
204
205 tmp2 = utf8tolatin1ifpossible (c);
206
207 rc = asprintf (&tmp, "%s:%s:%s", state->response.username,
208 state->response.realm ?
209 state->response.realm : "", tmp2);
210 free (tmp2);
211 if (rc < 0)
212 return GSASL_MALLOC_ERROR;
213
214 rc = gsasl_md5 (tmp, strlen (tmp), &tmp2);
215 free (tmp);
216 if (rc != GSASL_OK)
217 return rc;
218 memcpy (state->secret, tmp2, DIGEST_MD5_LENGTH);
219 free (tmp2);
220 }
221
222 rc = digest_md5_hmac (state->response.response,
223 state->secret,
224 state->response.nonce,
225 state->response.nc,
226 state->response.cnonce,
227 state->response.qop,
228 state->response.authzid,
229 state->response.digesturi,
230 0,
231 state->response.cipher,
232 state->kic, state->kis, state->kcc, state->kcs);
233 if (rc)
234 return GSASL_CRYPTO_ERROR;
235
236 *output = digest_md5_print_response (&state->response);
237 if (!*output)
238 return GSASL_AUTHENTICATION_ERROR;
239
240 *output_len = strlen (*output);
241
242 state->step++;
243 res = GSASL_NEEDS_MORE;
244 }
245 break;
246
247 case 2:
248 {
249 char check[DIGEST_MD5_RESPONSE_LENGTH + 1];
250
251 if (digest_md5_parse_finish (input, input_len, &state->finish) < 0)
252 return GSASL_MECHANISM_PARSE_ERROR;
253
254 res = digest_md5_hmac (check, state->secret,
255 state->response.nonce, state->response.nc,
256 state->response.cnonce, state->response.qop,
257 state->response.authzid,
258 state->response.digesturi, 1,
259 state->response.cipher, NULL, NULL, NULL,
260 NULL);
261 if (res != GSASL_OK)
262 break;
263
264 if (strcmp (state->finish.rspauth, check) == 0)
265 res = GSASL_OK;
266 else
267 res = GSASL_AUTHENTICATION_ERROR;
268 state->step++;
269 }
270 break;
271
272 default:
273 res = GSASL_MECHANISM_CALLED_TOO_MANY_TIMES;
274 break;
275 }
276
277 return res;
278}
279
280void
281_gsasl_digest_md5_client_finish (Gsasl_session * sctx, void *mech_data)
282{
283 _Gsasl_digest_md5_client_state *state = mech_data;
284
285 if (!state)
286 return;
287
288 digest_md5_free_challenge (&state->challenge);
289 digest_md5_free_response (&state->response);
290 digest_md5_free_finish (&state->finish);
291
292 free (state);
293}
294
295int
296_gsasl_digest_md5_client_encode (Gsasl_session * sctx,
297 void *mech_data,
298 const char *input,
299 size_t input_len,
300 char **output, size_t * output_len)
301{
302 _Gsasl_digest_md5_client_state *state = mech_data;
303 int res;
304
305 res = digest_md5_encode (input, input_len, output, output_len,
306 state->response.qop,
307 state->sendseqnum, state->kic);
308 if (res)
309 return res == -2 ? GSASL_NEEDS_MORE : GSASL_INTEGRITY_ERROR;
310
311 if (state->sendseqnum == 4294967295UL)
312 state->sendseqnum = 0;
313 else
314 state->sendseqnum++;
315
316 return GSASL_OK;
317}
318
319int
320_gsasl_digest_md5_client_decode (Gsasl_session * sctx,
321 void *mech_data,
322 const char *input,
323 size_t input_len,
324 char **output, size_t * output_len)
325{
326 _Gsasl_digest_md5_client_state *state = mech_data;
327 int res;
328
329 res = digest_md5_decode (input, input_len, output, output_len,
330 state->response.qop,
331 state->readseqnum, state->kis);
332 if (res)
333 return res == -2 ? GSASL_NEEDS_MORE : GSASL_INTEGRITY_ERROR;
334
335 if (state->readseqnum == 4294967295UL)
336 state->readseqnum = 0;
337 else
338 state->readseqnum++;
339
340 return GSASL_OK;
341}
342