Merge pull request #174 from abhinav-upadhyay/fix-krb5.conf.5
[heimdal.git] / lib / ntlm / digest.c
blob865f43662e01c0b6ce0eb52c9377df723d3a3912
1 /*
2 * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <unistd.h>
39 #include <CommonCrypto/CommonDigest.h>
40 #include <CommonCrypto/CommonHMAC.h>
41 #include <assert.h>
42 #include <roken.h>
43 #include <hex.h>
44 #include "heim-auth.h"
45 #include "ntlm_err.h"
47 struct heim_digest_desc {
48 #define F_SERVER 1
49 #define F_HAVE_HASH 2
50 #define F_HAVE_HA1 4
51 #define F_USE_PREFIX 8
52 int flags;
53 int type;
54 char *password;
55 uint8_t SecretHash[CC_MD5_DIGEST_LENGTH];
56 char *serverNonce;
57 char *serverRealm;
58 char *serverQOP;
59 char *serverMethod;
60 char *serverMaxbuf;
61 char *serverOpaque;
62 char *clientUsername;
63 char *clientResponse;
64 char *clientURI;
65 char *clientRealm;
66 char *clientNonce;
67 char *clientQOP;
68 char *clientNC;
69 char *serverAlgorithm;
70 char *auth_id;
72 /* internally allocated objects returned to caller */
73 char *serverChallenge;
74 char *clientReply;
75 char *serverReply;
78 #define FREE_AND_CLEAR(x) do { if ((x)) { free((x)); (x) = NULL; } } while(0)
79 #define MEMSET_FREE_AND_CLEAR(x) do { if ((x)) { memset(x, 0, strlen(x)); free((x)); (x) = NULL; } } while(0)
81 static const char digest_prefix[] = "Digest ";
83 static void
84 clear_context(heim_digest_t context)
86 MEMSET_FREE_AND_CLEAR(context->password);
87 memset(context->SecretHash, 0, sizeof(context->SecretHash));
88 context->flags &= ~(F_HAVE_HASH);
89 FREE_AND_CLEAR(context->serverNonce);
90 FREE_AND_CLEAR(context->serverRealm);
91 FREE_AND_CLEAR(context->serverQOP);
92 FREE_AND_CLEAR(context->serverMethod);
93 FREE_AND_CLEAR(context->serverMaxbuf);
94 FREE_AND_CLEAR(context->serverOpaque);
95 FREE_AND_CLEAR(context->clientUsername);
96 FREE_AND_CLEAR(context->clientResponse);
97 FREE_AND_CLEAR(context->clientURI);
98 FREE_AND_CLEAR(context->clientRealm);
99 FREE_AND_CLEAR(context->clientNonce);
100 FREE_AND_CLEAR(context->clientQOP);
101 FREE_AND_CLEAR(context->clientNC);
102 FREE_AND_CLEAR(context->serverAlgorithm);
103 FREE_AND_CLEAR(context->auth_id);
105 FREE_AND_CLEAR(context->serverChallenge);
106 FREE_AND_CLEAR(context->clientReply);
107 FREE_AND_CLEAR(context->serverReply);
110 static void
111 digest_userhash(const char *user, const char *realm, const char *password,
112 unsigned char md[CC_MD5_DIGEST_LENGTH])
114 CC_MD5_CTX ctx;
116 CC_MD5_Init(&ctx);
117 CC_MD5_Update(&ctx, user, (CC_LONG)strlen(user));
118 CC_MD5_Update(&ctx, ":", 1);
119 CC_MD5_Update(&ctx, realm, (CC_LONG)strlen(realm));
120 CC_MD5_Update(&ctx, ":", 1);
121 CC_MD5_Update(&ctx, password, (CC_LONG)strlen(password));
122 CC_MD5_Final(md, &ctx);
125 static char *
126 build_A1_hash(heim_digest_t context)
128 unsigned char md[CC_MD5_DIGEST_LENGTH];
129 CC_MD5_CTX ctx;
130 char *A1;
132 if (context->flags & F_HAVE_HA1) {
133 memcpy(md, context->SecretHash, sizeof(md));
134 } else if (context->flags & F_HAVE_HASH) {
135 memcpy(md, context->SecretHash, sizeof(md));
136 } else if (context->password) {
137 if (context->clientUsername == NULL)
138 return NULL;
139 if (context->serverRealm == NULL)
140 return NULL;
141 digest_userhash(context->clientUsername,
142 context->serverRealm,
143 context->password,
144 md);
145 } else
146 return NULL;
148 if ((context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS || context->type == HEIM_DIGEST_TYPE_RFC2831) && (context->flags & F_HAVE_HA1) == 0) {
149 if (context->serverNonce == NULL)
150 return NULL;
152 CC_MD5_Init(&ctx);
153 CC_MD5_Update(&ctx, md, sizeof(md));
154 memset(md, 0, sizeof(md));
155 CC_MD5_Update(&ctx, ":", 1);
156 CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce));
157 if (context->clientNonce) {
158 CC_MD5_Update(&ctx, ":", 1);
159 CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce));
161 if (context->type == HEIM_DIGEST_TYPE_RFC2831 && context->auth_id) {
162 CC_MD5_Update(&ctx, ":", 1);
163 CC_MD5_Update(&ctx, context->auth_id, (CC_LONG)strlen(context->auth_id));
165 CC_MD5_Final(md, &ctx);
167 hex_encode(md, sizeof(md), &A1);
168 if (A1)
169 strlwr(A1);
171 return A1;
174 static char *
175 build_A2_hash(heim_digest_t context, const char *method)
177 unsigned char md[CC_MD5_DIGEST_LENGTH];
178 CC_MD5_CTX ctx;
179 char *A2;
181 CC_MD5_Init(&ctx);
182 if (method)
183 CC_MD5_Update(&ctx, method, (CC_LONG)strlen(method));
184 CC_MD5_Update(&ctx, ":", 1);
185 CC_MD5_Update(&ctx, context->clientURI, (CC_LONG)strlen(context->clientURI));
187 /* conf|int */
188 if (context->type == HEIM_DIGEST_TYPE_RFC2831) {
189 if (strcasecmp(context->clientQOP, "auth-int") == 0 || strcasecmp(context->clientQOP, "auth-conf") == 0) {
190 /* XXX if we have a body hash, use that */
191 static char conf_zeros[] = ":00000000000000000000000000000000";
192 CC_MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1);
194 } else {
195 /* support auth-int ? */
196 if (context->clientQOP && strcasecmp(context->clientQOP, "auth") != 0)
197 return NULL;
200 CC_MD5_Final(md, &ctx);
202 hex_encode(md, sizeof(md), &A2);
203 if (A2)
204 strlwr(A2);
206 return A2;
213 struct md5_value {
214 char *mv_name;
215 char *mv_value;
216 struct md5_value *mv_next;
219 static void
220 free_values(struct md5_value *val)
222 struct md5_value *v;
223 while(val) {
224 v = val->mv_next;
225 if (val->mv_name)
226 free(val->mv_name);
227 if (val->mv_value)
228 free(val->mv_value);
229 free(val);
230 val = v;
235 * Search for entry, if found, remove entry and return string to be freed.
238 static char *
239 values_find(struct md5_value **val, const char *v)
241 struct md5_value *cur;
242 char *str;
244 while (*val != NULL) {
245 if (strcasecmp(v, (*val)->mv_name) == 0)
246 break;
247 val = &(*val)->mv_next;
249 if (*val == NULL)
250 return NULL;
251 cur = *val;
252 *val = (*val)->mv_next;
254 str = cur->mv_value;
255 free(cur->mv_name);
256 free(cur);
258 return str;
261 static int
262 parse_values(const char *string, struct md5_value **val)
264 struct md5_value *v;
265 size_t size;
266 char *str, *p1, *p2;
267 size_t sz;
269 *val = NULL;
271 if ((str = strdup(string)) == NULL)
272 return ENOMEM;
274 size = strlen(str);
276 p1 = str;
278 while (p1 - str < size) {
279 sz = strspn(p1, " \t\n\r,");
280 if (p1[sz] == '\0')
281 break;
282 p1 += sz;
283 sz = strcspn(p1, " \t\n\r=");
284 if (sz == 0 || p1[sz] == '\0')
285 goto error;
286 p2 = p1 + sz;
288 if ((v = malloc(sizeof(*v))) == NULL)
289 goto nomem;
290 v->mv_name = v->mv_value = NULL;
291 v->mv_next = *val;
292 *val = v;
293 if ((v->mv_name = malloc(p2 - p1 + 1)) == NULL)
294 goto nomem;
295 strncpy(v->mv_name, p1, p2 - p1);
296 v->mv_name[p2 - p1] = '\0';
298 sz = strspn(p2, " \t\n\r");
299 if (p2[sz] == '\0')
300 goto error;
301 p2 += sz;
303 if (*p2 != '=')
304 goto error;
305 p2++;
307 sz = strspn(p2, " \t\n\r");
308 if (p2[sz] == '\0')
309 goto error;
310 p2 += sz;
311 p1 = p2;
313 if (*p2 == '"') {
314 p1++;
315 while (*p2 == '"') {
316 p2++;
317 p2 = strchr(p2, '\"');
318 if (p2 == NULL)
319 goto error;
320 if (p2[0] == '\0')
321 goto error;
322 if (p2[-1] != '\\')
323 break;
325 } else {
326 sz = strcspn(p2, " \t\n\r=,");
327 p2 += sz;
330 #if 0 /* allow empty values */
331 if (p1 == p2)
332 goto error;
333 #endif
335 if ((v->mv_value = malloc(p2 - p1 + 1)) == NULL)
336 goto nomem;
337 strncpy(v->mv_value, p1, p2 - p1);
338 v->mv_value[p2 - p1] = '\0';
340 if (p2[0] == '\0')
341 break;
342 if (p2[0] == '"')
343 p2++;
345 sz = strspn(p2, " \t\n\r");
346 if (p2[sz] == '\0')
347 break;
348 p2 += sz;
350 if (p2[0] == '\0')
351 break;
352 if (p2[0] != ',')
353 goto error;
354 p1 = p2;
357 free(str);
359 return 0;
360 error:
361 free_values(*val);
362 *val = NULL;
363 free(str);
364 return EINVAL;
365 nomem:
366 free_values(*val);
367 *val = NULL;
368 free(str);
369 return ENOMEM;
376 static const char *
377 check_prefix(heim_digest_t context, const char *challenge)
379 if (strncasecmp(digest_prefix, challenge, sizeof(digest_prefix) - 1) == 0) {
381 challenge += sizeof(digest_prefix) - 1;
382 while (*challenge == 0x20) /* remove extra space */
383 challenge++;
384 context->flags |= F_USE_PREFIX;
387 return challenge;
394 heim_digest_t
395 heim_digest_create(int server, int type)
397 heim_digest_t context;
399 context = calloc(1, sizeof(*context));
400 if (context == NULL)
401 return NULL;
402 context->flags |= F_SERVER;
403 context->type = type;
405 return context;
408 static char *
409 generate_nonce(void)
411 uint8_t rand[8];
412 char *nonce;
414 if (CCRandomCopyBytes(kCCRandomDefault, rand, sizeof(rand)) != kCCSuccess)
415 return NULL;
417 if (rk_hex_encode(rand, sizeof(rand), &nonce) < 0)
418 return NULL;
420 return nonce;
424 * Generate a challange, needs to set serverRealm before calling this function.
426 * If type is set to HEIM_DIGEST_TYPE_AUTO, the HEIM_DIGEST_TYPE_RFC2831 will be used instead.
428 * For RFC2617 and RFC2831 QOP is required, so if any qop other then "auth" is requested, it need to be set with heim_diest_set_key().
430 * @return returns the challenge or NULL on error or failure to build the string. the lifetime
431 * of the string is manage by heim_digest and last until the the context is
432 * freed or until next call to heim_digest_generate_challenge().
435 const char *
436 heim_digest_generate_challenge(heim_digest_t context)
438 char *challenge = NULL;
440 if (context->serverRealm == NULL)
441 return NULL;
443 if (context->serverNonce == NULL) {
444 if ((context->serverNonce = generate_nonce()) == NULL)
445 return NULL;
448 if (context->serverQOP == NULL) {
449 if ((context->serverQOP = strdup("auth")) == NULL)
450 return NULL;
453 if (context->serverMaxbuf == NULL) {
454 if ((context->serverMaxbuf = strdup("65536")) == NULL)
455 return NULL;
458 switch(context->type) {
459 case HEIM_DIGEST_TYPE_RFC2617_MD5:
460 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5,qop=\"%s\"",
461 context->serverRealm, context->serverNonce,
462 context->serverQOP);
463 break;
464 case HEIM_DIGEST_TYPE_RFC2617_MD5_SESS:
465 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5-sess,qop=\"%s\"",
466 context->serverRealm, context->serverNonce, context->serverQOP);
467 break;
468 case HEIM_DIGEST_TYPE_RFC2069:
469 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\"",
470 context->serverRealm, context->serverNonce);
471 break;
472 case HEIM_DIGEST_TYPE_AUTO:
473 context->type = HEIM_DIGEST_TYPE_RFC2831;
474 /* FALL THOUGH */
475 case HEIM_DIGEST_TYPE_RFC2831:
476 asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",qop=\"%s\",algorithm=md5-sess,charset=utf-8,maxbuf=%s",
477 context->serverRealm, context->serverNonce, context->serverQOP, context->serverMaxbuf);
478 break;
481 FREE_AND_CLEAR(context->serverChallenge);
482 context->serverChallenge = challenge;
484 return challenge;
488 heim_digest_parse_challenge(heim_digest_t context, const char *challenge)
490 struct md5_value *val = NULL;
491 int ret, type;
493 challenge = check_prefix(context, challenge);
495 ret = parse_values(challenge, &val);
496 if (ret)
497 goto out;
499 ret = 1;
501 context->serverNonce = values_find(&val, "nonce");
502 if (context->serverNonce == NULL) goto out;
504 context->serverRealm = values_find(&val, "realm");
505 if (context->serverRealm == NULL) goto out;
507 /* check alg */
509 context->serverAlgorithm = values_find(&val, "algorithm");
510 if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) {
511 type = HEIM_DIGEST_TYPE_RFC2617_MD5;
512 } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) {
513 type = HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831;
514 } else {
515 goto out;
518 context->serverQOP = values_find(&val, "qop");
519 if (context->serverQOP == NULL)
520 type = HEIM_DIGEST_TYPE_RFC2069;
522 context->serverOpaque = values_find(&val, "opaque");
524 if (context->type != HEIM_DIGEST_TYPE_AUTO && (context->type & type) == 0)
525 goto out;
526 else if (context->type == HEIM_DIGEST_TYPE_AUTO)
527 context->type = type;
529 ret = 0;
530 out:
531 free_values(val);
532 if (ret)
533 clear_context(context);
534 return ret;
538 static void
539 set_auth_method(heim_digest_t context)
542 if (context->serverMethod == NULL) {
543 if (context->type == HEIM_DIGEST_TYPE_RFC2831)
544 context->serverMethod = strdup("AUTHENTICATE");
545 else
546 context->serverMethod = strdup("GET");
551 heim_digest_parse_response(heim_digest_t context, const char *response)
553 struct md5_value *val = NULL;
554 char *nonce;
555 int ret;
557 response = check_prefix(context, response);
559 ret = parse_values(response, &val);
560 if (ret)
561 goto out;
563 ret = 1;
565 if (context->type == HEIM_DIGEST_TYPE_AUTO) {
566 goto out;
567 } else if (context->type == HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831) {
568 context->clientURI = values_find(&val, "uri");
569 if (context->clientURI) {
570 context->type = HEIM_DIGEST_TYPE_RFC2617_MD5_SESS;
571 } else {
572 context->clientURI = values_find(&val, "digest-uri");
573 context->type = HEIM_DIGEST_TYPE_RFC2831;
575 } else if (context->type == HEIM_DIGEST_TYPE_RFC2831) {
576 context->clientURI = values_find(&val, "digest-uri");
577 } else {
578 context->clientURI = values_find(&val, "uri");
581 if (context->clientURI == NULL)
582 goto out;
584 context->clientUsername = values_find(&val, "username");
585 if (context->clientUsername == NULL) goto out;
587 /* if client sent realm, make sure its the same of serverRealm if its set */
588 context->clientRealm = values_find(&val, "realm");
589 if (context->clientRealm && context->serverRealm && strcmp(context->clientRealm, context->serverRealm) != 0)
590 goto out;
592 context->clientResponse = values_find(&val, "response");
593 if (context->clientResponse == NULL) goto out;
595 nonce = values_find(&val, "nonce");
596 if (nonce == NULL) goto out;
598 if (strcmp(nonce, context->serverNonce) != 0) {
599 free(nonce);
600 goto out;
602 free(nonce);
604 if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
606 context->clientQOP = values_find(&val, "qop");
607 if (context->clientQOP == NULL) goto out;
610 * If we have serverQOP, lets check that clientQOP exists
611 * in the list of server entries.
614 if (context->serverQOP) {
615 Boolean found = false;
616 char *b, *e;
617 size_t len, clen = strlen(context->clientQOP);
619 b = context->serverQOP;
620 while (b && !found) {
621 e = strchr(b, ',');
622 if (e == NULL)
623 len = strlen(b);
624 else {
625 len = e - b;
626 e += 1;
628 if (clen == len && strncmp(b, context->clientQOP, len) == 0)
629 found = true;
630 b = e;
632 if (!found)
633 goto out;
636 context->clientNC = values_find(&val, "nc");
637 if (context->clientNC == NULL) goto out;
639 context->clientNonce = values_find(&val, "cnonce");
640 if (context->clientNonce == NULL) goto out;
643 set_auth_method(context);
645 ret = 0;
646 out:
647 free_values(val);
648 return ret;
651 char *
652 heim_digest_userhash(const char *user, const char *realm, const char *password)
654 unsigned char md[CC_MD5_DIGEST_LENGTH];
655 char *str = NULL;
657 digest_userhash(user, realm, password, md);
659 hex_encode(md, sizeof(md), &str);
660 if (str)
661 strlwr(str);
662 return str;
665 static char *
666 build_digest(heim_digest_t context, const char *a1, const char *method)
668 CC_MD5_CTX ctx;
669 uint8_t md[CC_MD5_DIGEST_LENGTH];
670 char *a2, *str = NULL;
672 a2 = build_A2_hash(context, method);
673 if (a2 == NULL)
674 return NULL;
676 CC_MD5_Init(&ctx);
677 CC_MD5_Update(&ctx, a1, (CC_LONG)strlen(a1));
678 CC_MD5_Update(&ctx, ":", 1);
679 CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce));
680 if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
681 CC_MD5_Update(&ctx, ":", 1);
682 CC_MD5_Update(&ctx, context->clientNC, (CC_LONG)strlen(context->clientNC));
683 CC_MD5_Update(&ctx, ":", 1);
684 CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce));
685 CC_MD5_Update(&ctx, ":", 1);
686 CC_MD5_Update(&ctx, context->clientQOP, (CC_LONG)strlen(context->clientQOP));
688 CC_MD5_Update(&ctx, ":", 1);
689 CC_MD5_Update(&ctx, a2, (CC_LONG)strlen(a2));
690 CC_MD5_Final(md, &ctx);
692 free(a2);
694 hex_encode(md, sizeof(md), &str);
695 if (str)
696 strlwr(str);
698 return str;
701 static void
702 build_server_response(heim_digest_t context, char *a1, char **response)
704 char *str;
706 str = build_digest(context, a1, NULL);
707 if (str == NULL)
708 return;
710 FREE_AND_CLEAR(context->serverReply);
711 asprintf(&context->serverReply, "%srspauth=%s",
712 (context->flags & F_USE_PREFIX) ? digest_prefix : "",
713 str);
714 free(str);
715 if (response)
716 *response = context->serverReply;
721 * Create response from server to client to server, server verification is in response.
722 * clientUsername and clientURI have to be given.
723 * If realm is not set, its used from server.
726 const char *
727 heim_digest_create_response(heim_digest_t context, char **response)
729 char *a1, *str, *cnonce = NULL, *opaque = NULL, *uri = NULL, *nc = NULL;
731 if (response)
732 *response = NULL;
734 if (context->clientUsername == NULL || context->clientURI == NULL)
735 return NULL;
737 if (context->clientRealm == NULL) {
738 if (context->serverRealm == NULL)
739 return NULL;
740 if ((context->clientRealm = strdup(context->serverRealm)) == NULL)
741 return NULL;
744 if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
745 if (context->clientNC == NULL) {
746 if ((context->clientNC = strdup("00000001")) == NULL)
747 return NULL;
749 if (context->clientNonce == NULL) {
750 if ((context->clientNonce = generate_nonce()) == NULL)
751 return NULL;
755 * If using non RFC2069, appropriate QOP should be set.
757 * Pick QOP from server if not given, if its a list, pick the first entry
759 if (context->clientQOP == NULL) {
760 char *r;
761 if (context->serverQOP == NULL)
762 return NULL;
763 r = strchr(context->serverQOP, ',');
764 if (r == NULL) {
765 if ((context->clientQOP = strdup(context->serverQOP)) == NULL)
766 return NULL;
767 } else {
768 size_t len = (r - context->serverQOP) + 1;
769 if ((context->clientQOP = malloc(len)) == NULL)
770 return NULL;
771 strlcpy(context->clientQOP, context->serverQOP, len);
776 set_auth_method(context);
778 a1 = build_A1_hash(context);
779 if (a1 == NULL)
780 return NULL;
782 str = build_digest(context, a1, context->serverMethod);
783 if (str == NULL) {
784 MEMSET_FREE_AND_CLEAR(a1);
785 return NULL;
788 MEMSET_FREE_AND_CLEAR(context->clientResponse);
789 context->clientResponse = str;
791 if (context->clientURI) {
792 const char *name = "digest-uri";
793 if (context->type != HEIM_DIGEST_TYPE_RFC2831)
794 name = "uri";
795 asprintf(&uri, ",%s=\"%s\"", name, context->clientURI);
798 if (context->serverOpaque)
799 asprintf(&opaque, ",opaque=\"%s\"", context->serverOpaque);
801 if (context->clientNonce)
802 asprintf(&cnonce, ",cnonce=\"%s\"", context->clientNonce);
804 if (context->clientNC)
805 asprintf(&nc, ",nc=%s", context->clientNC);
807 asprintf(&context->clientReply,
808 "username=%s,realm=%s,nonce=\"%s\",qop=\"%s\"%s%s%s,response=\"%s\"%s",
809 context->clientUsername, context->clientRealm,
810 context->serverNonce,
811 context->clientQOP,
812 uri ? uri : "",
813 cnonce ? cnonce : "",
814 nc ? nc : "",
815 context->clientResponse,
816 opaque ? opaque : "");
818 build_server_response(context, a1, response);
819 MEMSET_FREE_AND_CLEAR(a1);
820 FREE_AND_CLEAR(uri);
821 FREE_AND_CLEAR(opaque);
822 FREE_AND_CLEAR(cnonce);
823 FREE_AND_CLEAR(nc);
825 return context->clientReply;
829 heim_digest_verify(heim_digest_t context, char **response)
831 char *a1;
832 char *str;
833 int res;
835 if (response)
836 *response = NULL;
838 set_auth_method(context);
840 a1 = build_A1_hash(context);
841 if (a1 == NULL)
842 return ENOMEM;
844 str = build_digest(context, a1, context->serverMethod);
845 if (str == NULL) {
846 MEMSET_FREE_AND_CLEAR(a1);
847 return ENOMEM;
850 res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL;
851 free(str);
852 if (res) {
853 MEMSET_FREE_AND_CLEAR(a1);
854 return res;
857 /* build server_response */
858 build_server_response(context, a1, response);
859 MEMSET_FREE_AND_CLEAR(a1);
860 /* XXX break ABI and return internally allocated string instead */
861 if (response)
862 *response = strdup(*response);
864 return 0;
868 * Create a rspauth= response.
869 * Assumes that the A1hash/password serverNonce, clientNC, clientNonce, clientQOP is set.
871 * @return the rspauth string (including rspauth), return key are stored in serverReply and will be invalid after another call to heim_digest_*
874 const char *
875 heim_digest_server_response(heim_digest_t context)
877 char *a1;
879 if (context->serverNonce == NULL)
880 return NULL;
881 if (context->clientURI == NULL)
882 return NULL;
884 a1 = build_A1_hash(context);
885 if (a1 == NULL)
886 return NULL;
888 build_server_response(context, a1, NULL);
889 MEMSET_FREE_AND_CLEAR(a1);
891 return context->serverReply;
894 void
895 heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize)
899 void
900 heim_digest_release(heim_digest_t context)
902 clear_context(context);
903 free(context);
906 struct {
907 char *name;
908 size_t offset;
909 } keys[] = {
910 #define KVN(value) { #value, offsetof(struct heim_digest_desc, value) }
911 KVN(serverNonce),
912 KVN(serverRealm),
913 KVN(serverQOP),
914 KVN(serverMethod),
915 { "method", offsetof(struct heim_digest_desc, serverMethod) },
916 KVN(serverMaxbuf),
917 KVN(clientUsername),
918 { "username", offsetof(struct heim_digest_desc, clientUsername) },
919 KVN(clientResponse),
920 KVN(clientURI),
921 { "uri", offsetof(struct heim_digest_desc, clientURI) },
922 KVN(clientRealm),
923 { "realm", offsetof(struct heim_digest_desc, clientRealm) },
924 KVN(clientNonce),
925 KVN(clientQOP),
926 KVN(clientNC),
927 KVN(serverAlgorithm),
928 KVN(auth_id)
929 #undef KVN
932 const char *
933 heim_digest_get_key(heim_digest_t context, const char *key)
935 size_t n;
937 for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) {
938 if (strcasecmp(key, keys[n].name) == 0) {
939 char **ptr = (char **)((((char *)context) + keys[n].offset));
940 return *ptr;
943 return NULL;
947 heim_digest_set_key(heim_digest_t context, const char *key, const char *value)
950 if (strcmp(key, "password") == 0) {
951 FREE_AND_CLEAR(context->password);
952 if ((context->password = strdup(value)) == NULL)
953 return ENOMEM;
954 context->flags &= ~(F_HAVE_HASH|F_HAVE_HA1);
955 } else if (strcmp(key, "userhash") == 0) {
956 ssize_t ret;
957 FREE_AND_CLEAR(context->password);
959 ret = hex_decode(value, context->SecretHash, sizeof(context->SecretHash));
960 if (ret != sizeof(context->SecretHash))
961 return EINVAL;
962 context->flags &= ~F_HAVE_HA1;
963 context->flags |= F_HAVE_HASH;
964 } else if (strcmp(key, "H(A1)") == 0) {
965 ssize_t ret;
966 FREE_AND_CLEAR(context->password);
968 ret = hex_decode(value, context->SecretHash, sizeof(context->SecretHash));
969 if (ret != sizeof(context->SecretHash))
970 return EINVAL;
971 context->flags &= ~F_HAVE_HASH;
972 context->flags |= F_HAVE_HA1;
973 } else if (strcmp(key, "method") == 0) {
974 FREE_AND_CLEAR(context->serverMethod);
975 if ((context->serverMethod = strdup(value)) == NULL)
976 return ENOMEM;
977 } else {
978 size_t n;
980 for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) {
981 if (strcasecmp(key, keys[n].name) == 0) {
982 char **ptr = (char **)((((char *)context) + keys[n].offset));
983 FREE_AND_CLEAR(*ptr);
984 if (((*ptr) = strdup(value)) == NULL)
985 return ENOMEM;
986 break;
989 if (n == sizeof(keys) / sizeof(keys[0]))
990 return ENOENT;
992 return 0;