Initial release, version 0.0.0.
[gsasl.git] / lib / cram-md5.c
blob8070dbd6dd3e907b13167919c68ca3fbe0b5e2eb
1 /* cram-md5.c implementation of SASL mechanism CRAM-MD5 from RFC 2195
2 * Copyright (C) 2002 Simon Josefsson
4 * This file is part of libgsasl.
6 * Libgsasl is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * Libgsasl 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.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with libgsasl; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "internal.h"
24 #ifdef USE_CRAM_MD5
26 #include <gcrypt.h>
28 int
29 _gsasl_cram_md5_client_init (Gsasl_ctx *ctx)
31 int res;
33 if (gcry_check_version(GCRYPT_VERSION) == NULL)
34 return GSASL_GCRYPT_ERROR;
36 res = gcry_control (GCRYCTL_INIT_SECMEM, 512, 0);
37 if (res != GCRYERR_SUCCESS)
38 return GSASL_GCRYPT_ERROR;
40 return GSASL_OK;
43 void
44 _gsasl_cram_md5_client_done (Gsasl_ctx *ctx)
46 return;
49 int
50 _gsasl_cram_md5_client_start (Gsasl_session_ctx *cctx,
51 void **mech_data)
53 Gsasl_ctx *ctx;
54 int *done;
56 ctx = gsasl_client_ctx_get (cctx);
57 if (ctx == NULL)
58 return GSASL_CANNOT_GET_CTX;
60 if (gsasl_client_callback_authentication_id_get (ctx) == NULL)
61 return GSASL_NEED_CLIENT_AUTHENTICATION_ID_CALLBACK;
63 if (gsasl_client_callback_password_get (ctx) == NULL)
64 return GSASL_NEED_CLIENT_PASSWORD_CALLBACK;
66 done = (int*) malloc(sizeof(*done));
67 if (done == NULL)
68 return GSASL_MALLOC_ERROR;
70 *done = 0;
72 *mech_data = done;
74 return GSASL_OK;
77 int
78 _gsasl_cram_md5_client_step (Gsasl_session_ctx *cctx,
79 void *mech_data,
80 const char *input,
81 size_t input_len,
82 char *output,
83 size_t *output_len)
85 int *step = mech_data;
86 Gsasl_ctx *ctx;
87 Gsasl_client_callback_authentication_id cb_authentication_id;
88 Gsasl_client_callback_password cb_password;
89 GCRY_MD_HD md5h;
90 char *hash;
91 int hash_len = gcry_md_get_algo_dlen (GCRY_MD_MD5);
92 size_t len;
93 char *tmp;
94 int i;
95 int res;
97 if (*step > 0)
98 return GSASL_OK;
100 if (input_len == 0)
102 *output_len = 0;
103 return GSASL_NEEDS_MORE;
106 ctx = gsasl_client_ctx_get (cctx);
107 if (ctx == NULL)
108 return GSASL_CANNOT_GET_CTX;
110 cb_authentication_id = gsasl_client_callback_authentication_id_get (ctx);
111 if (cb_authentication_id == NULL)
112 return GSASL_NEED_CLIENT_AUTHENTICATION_ID_CALLBACK;
114 cb_password = gsasl_client_callback_password_get (ctx);
115 if (cb_password == NULL)
116 return GSASL_NEED_CLIENT_PASSWORD_CALLBACK;
118 md5h = gcry_md_open (GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
119 if (md5h == NULL)
120 return GSASL_GCRYPT_ERROR;
122 /* XXX? password stored in callee's output buffer */
123 len = *output_len;
124 res = cb_password (cctx, output, &len);
125 if (res != GSASL_OK)
126 return res;
127 tmp = gsasl_utf8_nfkc_normalize (output, len);
128 if (tmp == NULL)
129 return GSASL_UNICODE_NORMALIZATION_ERROR;
130 res = gcry_md_setkey (md5h, tmp, strlen(tmp));
131 free(tmp);
132 if (res != GCRYERR_SUCCESS)
133 return GSASL_GCRYPT_ERROR;
135 gcry_md_write (md5h, input, input_len);
137 hash = gcry_md_read (md5h, GCRY_MD_MD5);
138 if (hash == NULL)
139 return GSASL_GCRYPT_ERROR;
141 len = *output_len;
142 res = cb_authentication_id (cctx, output, &len);
143 if (res != GSASL_OK)
144 return res;
145 tmp = gsasl_utf8_nfkc_normalize (output, len);
146 if (tmp == NULL)
147 return GSASL_UNICODE_NORMALIZATION_ERROR;
148 if (strlen(tmp) + strlen(" ") + 2*hash_len >= *output_len)
150 free(tmp);
151 return GSASL_TOO_SMALL_BUFFER;
153 len = strlen(tmp);
154 memcpy(output, tmp, len);
155 free(tmp);
156 output[len++] = ' ';
158 for (i = 0; i < hash_len; i++)
160 output[len + 2*i + 1] = HEXCHAR(hash[i]);
161 output[len + 2*i + 0] = HEXCHAR(hash[i] >> 4);
163 *output_len = len + 2*hash_len;
165 gcry_md_close(md5h);
167 (*step)++;
169 return GSASL_NEEDS_MORE;
173 _gsasl_cram_md5_client_finish (Gsasl_session_ctx *cctx,
174 void *mech_data)
176 int *step = mech_data;
178 free(step);
180 return GSASL_OK;
183 /* Server */
186 _gsasl_cram_md5_server_init (Gsasl_ctx *ctx)
188 if (gcry_check_version(GCRYPT_VERSION) == NULL)
189 return GSASL_GCRYPT_ERROR;
191 return GSASL_OK;
194 void
195 _gsasl_cram_md5_server_done (Gsasl_ctx *ctx)
197 return;
201 _gsasl_cram_md5_server_start (Gsasl_session_ctx *sctx,
202 void **mech_data)
204 Gsasl_ctx *ctx;
205 char *challenge;
206 int i;
208 ctx = gsasl_server_ctx_get (sctx);
209 if (ctx == NULL)
210 return GSASL_CANNOT_GET_CTX;
212 if (gsasl_server_callback_cram_md5_get (ctx) == NULL &&
213 gsasl_server_callback_retrieve_get (ctx) == NULL)
214 return GSASL_NEED_SERVER_CRAM_MD5_CALLBACK;
216 /* XXX this is ad-hoc and uses "localhost" instead of FQDN */
218 #define START_OF_XS 1
219 #define NUMBER_OF_XS 16
220 #define CHALLENGE_FORMAT "<XXXXXXXXXXXXXXXX.libgsasl@localhost>"
222 challenge = (char*) malloc(strlen(CHALLENGE_FORMAT) + 1);
223 if (challenge == NULL)
224 return GSASL_MALLOC_ERROR;
226 strcpy(challenge, CHALLENGE_FORMAT);
228 gcry_randomize (challenge + 1, NUMBER_OF_XS, GCRY_WEAK_RANDOM);
230 for (i = 0; i < NUMBER_OF_XS/2; i++)
232 challenge[START_OF_XS + NUMBER_OF_XS/2 + i] =
233 HEXCHAR(challenge[START_OF_XS + i]);
234 challenge[START_OF_XS + i] =
235 HEXCHAR(challenge[START_OF_XS + i] >> 4);
238 *mech_data = challenge;
240 return GSASL_OK;
244 _gsasl_cram_md5_server_step (Gsasl_session_ctx *sctx,
245 void *mech_data,
246 const char *input,
247 size_t input_len,
248 char *output,
249 size_t *output_len)
251 char *challenge = mech_data;
252 Gsasl_server_callback_cram_md5 cb_cram_md5;
253 Gsasl_server_callback_retrieve cb_retrieve;
254 int hash_len = gcry_md_get_algo_dlen (GCRY_MD_MD5);
255 char *username = NULL;
256 Gsasl_ctx *ctx;
257 int res;
259 if (input_len == 0)
261 if (*output_len < strlen(challenge))
262 return GSASL_TOO_SMALL_BUFFER;
264 *output_len = strlen(challenge);
265 memcpy(output, challenge, *output_len);
267 return GSASL_NEEDS_MORE;
270 if (input_len <= hash_len * 2)
271 return GSASL_MECHANISM_PARSE_ERROR;
273 if (input[input_len - hash_len * 2 - 1] != ' ')
274 return GSASL_MECHANISM_PARSE_ERROR;
276 ctx = gsasl_server_ctx_get (sctx);
277 if (ctx == NULL)
278 return GSASL_CANNOT_GET_CTX;
280 cb_cram_md5 = gsasl_server_callback_cram_md5_get (ctx);
281 cb_retrieve = gsasl_server_callback_retrieve_get (ctx);
282 if (cb_cram_md5 == NULL && cb_retrieve == NULL)
283 return GSASL_NEED_SERVER_CRAM_MD5_CALLBACK;
285 username = (char*) malloc(input_len);
286 if (username == NULL)
287 return GSASL_MALLOC_ERROR;
289 memcpy(username, input, input_len - hash_len * 2);
290 username[input_len - hash_len * 2 - 1] = '\0';
292 if (cb_cram_md5)
294 char *response;
296 response = (char*) malloc(hash_len * 2 + 1);
297 if (response == NULL)
299 free(username);
300 return GSASL_MALLOC_ERROR;
303 memcpy(response, input + input_len - hash_len * 2, hash_len * 2);
304 response[hash_len * 2 + 1] = '\0';
306 res = cb_cram_md5 (sctx, username, challenge, response);
308 free(response);
310 else if (cb_retrieve)
312 GCRY_MD_HD md5h;
313 char *hash;
314 size_t len;
315 size_t keylen;
316 char *key;
317 char *normkey;
318 int i;
320 md5h = gcry_md_open (GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
321 if (md5h == NULL)
323 free(username);
324 return GSASL_GCRYPT_ERROR;
327 res = cb_retrieve(sctx, username, NULL, NULL, NULL, &keylen);
328 if (res != GSASL_OK)
329 return res;
330 key = malloc(keylen);
331 if (key == NULL)
332 return GSASL_MALLOC_ERROR;
333 res = cb_retrieve(sctx, username, NULL, NULL, key, &keylen);
334 if (res != GSASL_OK)
336 free(username);
337 free(key);
338 return res;
340 normkey = gsasl_utf8_nfkc_normalize (key, keylen);
341 free(key);
342 if (normkey == NULL)
344 free(username);
345 return GSASL_UNICODE_NORMALIZATION_ERROR;
348 res = gcry_md_setkey (md5h, normkey, strlen(normkey));
349 free(normkey);
350 if (res != GCRYERR_SUCCESS)
352 free(username);
353 return GSASL_GCRYPT_ERROR;
356 gcry_md_write (md5h, challenge, strlen(challenge));
358 hash = gcry_md_read (md5h, GCRY_MD_MD5);
359 if (hash == NULL)
361 free(username);
362 return GSASL_GCRYPT_ERROR;
365 res = GSASL_OK;
366 for (i = 0; i < hash_len; i++)
367 if ((input[input_len - hash_len*2 + 2*i + 1] != HEXCHAR(hash[i])) ||
368 (input[input_len - hash_len*2 + 2*i + 0] != HEXCHAR(hash[i] >> 4)))
369 res = GSASL_AUTHENTICATION_ERROR;
372 free(username);
374 return res;
378 _gsasl_cram_md5_server_finish (Gsasl_session_ctx *sctx,
379 void *mech_data)
381 char *challenge = mech_data;
383 free(challenge);
385 return GSASL_OK;
388 #endif /* USE_CRAM_MD5 */