1/*
2 * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
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.
18 *
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.
22 *
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.
34 */
35
36#include <sys/types.h>
37#include <stdio.h>
38#include <unistd.h>
39#include <CommonCrypto/CommonDigest.h>
40#include <CommonCrypto/CommonRandomSPI.h>
41#include <CommonCrypto/CommonHMAC.h>
42#include <assert.h>
43#include <roken.h>
44#include <hex.h>
45#include "heim-auth.h"
46#include "ntlm_err.h"
47
48struct heim_digest_desc {
49#define F_SERVER	1
50#define F_HAVE_HASH	2
51#define F_HAVE_HA1	4
52#define F_USE_PREFIX	8
53    int flags;
54    int type;
55    char *password;
56    char *secretHash;
57    char *serverNonce;
58    char *serverRealm;
59    char *serverQOP;
60    char *serverMethod;
61    char *serverMaxbuf;
62    char *serverOpaque;
63    char *clientUsername;
64    char *clientResponse;
65    char *clientURI;
66    char *clientRealm;
67    char *clientNonce;
68    char *clientQOP;
69    char *clientNC;
70    char *serverAlgorithm;
71    char *auth_id;
72
73    /* internally allocated objects returned to caller */
74    char *serverChallenge;
75    char *clientReply;
76    char *serverReply;
77};
78
79#define FREE_AND_CLEAR(x) do { if ((x)) { free((x)); (x) = NULL; } } while(0)
80#define MEMSET_FREE_AND_CLEAR(x) do { if ((x)) { memset(x, 0, strlen(x)); free((x)); (x) = NULL; } } while(0)
81
82static const char digest_prefix[] = "Digest ";
83
84static void
85clear_context(heim_digest_t context)
86{
87    MEMSET_FREE_AND_CLEAR(context->password);
88    FREE_AND_CLEAR(context->secretHash);
89    context->flags &= ~(F_HAVE_HASH|F_HAVE_HA1);
90    FREE_AND_CLEAR(context->serverNonce);
91    FREE_AND_CLEAR(context->serverRealm);
92    FREE_AND_CLEAR(context->serverQOP);
93    FREE_AND_CLEAR(context->serverMethod);
94    FREE_AND_CLEAR(context->serverMaxbuf);
95    FREE_AND_CLEAR(context->serverOpaque);
96    FREE_AND_CLEAR(context->clientUsername);
97    FREE_AND_CLEAR(context->clientResponse);
98    FREE_AND_CLEAR(context->clientURI);
99    FREE_AND_CLEAR(context->clientRealm);
100    FREE_AND_CLEAR(context->clientNonce);
101    FREE_AND_CLEAR(context->clientQOP);
102    FREE_AND_CLEAR(context->clientNC);
103    FREE_AND_CLEAR(context->serverAlgorithm);
104    FREE_AND_CLEAR(context->auth_id);
105
106    FREE_AND_CLEAR(context->serverChallenge);
107    FREE_AND_CLEAR(context->clientReply);
108    FREE_AND_CLEAR(context->serverReply);
109}
110
111static char *
112MD5_Final_hex(CC_MD5_CTX *ctx)
113{
114    unsigned char md[CC_MD5_DIGEST_LENGTH];
115    char *hex = NULL;
116
117    CC_MD5_Final(md, ctx);
118
119    hex_encode(md, sizeof(md), &hex);
120
121    memset(md, 0, sizeof(md));
122    memset(ctx, 0, sizeof(*ctx));
123
124    if (hex)
125	strlwr(hex);
126    return hex;
127}
128
129static char *
130build_A1_hash(heim_digest_t context, int old_broken)
131{
132    CC_MD5_CTX ctx;
133    char *userhash = NULL;
134    char *A1;
135
136    if ((context->flags & F_HAVE_HASH) || (context->flags & F_HAVE_HA1)) {
137	userhash = strdup(context->secretHash);
138    } else if (context->password) {
139	if (context->clientUsername == NULL)
140	    return NULL;
141	if (context->password == NULL)
142	    return NULL;
143	if (context->serverRealm == NULL)
144	    return NULL;
145
146	userhash = heim_digest_userhash(context->clientUsername,
147					context->serverRealm,
148					context->password);
149    } else
150	return NULL;
151
152    if (userhash == NULL)
153	return NULL;
154
155    if ((context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS || context->type == HEIM_DIGEST_TYPE_RFC2831) && !(context->flags & F_HAVE_HA1)) {
156	if (context->serverNonce == NULL) {
157	    memset(userhash, 0, strlen(userhash));
158	    free(userhash);
159	    return NULL;
160	}
161
162	CC_MD5_Init(&ctx);
163
164	/*
165	 * SASL (RFC2831) uses the raw hash for the inner hash, keep that
166	 */
167	if (context->type == HEIM_DIGEST_TYPE_RFC2831 || old_broken) {
168	    uint8_t md[CC_MD5_DIGEST_LENGTH];
169	    if (hex_decode(userhash, md, sizeof(md)) != CC_MD5_DIGEST_LENGTH) {
170		memset(userhash, 0, strlen(userhash));
171		free(userhash);
172		return ENOMEM;
173	    }
174	    CC_MD5_Update(&ctx, md, sizeof(md));
175	    memset(md, 0, sizeof(md));
176
177	} else {
178	    CC_MD5_Update(&ctx, userhash, (CC_LONG)strlen(userhash));
179	}
180	memset(userhash, 0, strlen(userhash));
181	free(userhash);
182
183	CC_MD5_Update(&ctx, ":", 1);
184	CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce));
185	if (context->clientNonce) {
186	    CC_MD5_Update(&ctx, ":", 1);
187	    CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce));
188	}
189	if (context->type == HEIM_DIGEST_TYPE_RFC2831 && context->auth_id) {
190	    CC_MD5_Update(&ctx, ":", 1);
191	    CC_MD5_Update(&ctx, context->auth_id, (CC_LONG)strlen(context->auth_id));
192	}
193	A1 = MD5_Final_hex(&ctx);
194    } else {
195	A1 = userhash;
196    }
197
198    return A1;
199}
200
201static char *
202build_A2_hash(heim_digest_t context, const char *method)
203{
204    CC_MD5_CTX ctx;
205
206    CC_MD5_Init(&ctx);
207    if (method)
208	CC_MD5_Update(&ctx, method, (CC_LONG)strlen(method));
209    CC_MD5_Update(&ctx, ":", 1);
210    CC_MD5_Update(&ctx, context->clientURI, (CC_LONG)strlen(context->clientURI));
211
212    /* conf|int */
213    if (context->type == HEIM_DIGEST_TYPE_RFC2831) {
214	if (strcasecmp(context->clientQOP, "auth-int") == 0 || strcasecmp(context->clientQOP, "auth-conf") == 0) {
215	    /* XXX if we have a body hash, use that */
216	    static char conf_zeros[] = ":00000000000000000000000000000000";
217	    CC_MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1);
218	}
219    } else {
220	/* support auth-int ? */
221	if (context->clientQOP && strcasecmp(context->clientQOP, "auth") != 0)
222	    return NULL;
223    }
224
225    return MD5_Final_hex(&ctx);
226}
227
228/*
229 *
230 */
231
232struct md5_value {
233    char		*mv_name;
234    char		*mv_value;
235    struct md5_value 	*mv_next;
236};
237
238static void
239free_values(struct md5_value *val)
240{
241    struct md5_value *v;
242    while(val) {
243	v = val->mv_next;
244	if (val->mv_name)
245	    free(val->mv_name);
246	if (val->mv_value)
247	    free(val->mv_value);
248	free(val);
249	val = v;
250    }
251}
252
253/*
254 * Search for entry, if found, remove entry and return string to be freed.
255 */
256
257static char *
258values_find(struct md5_value **val, const char *v)
259{
260    struct md5_value *cur;
261    char *str;
262
263    while (*val != NULL) {
264	if (strcasecmp(v, (*val)->mv_name) == 0)
265	    break;
266	val = &(*val)->mv_next;
267    }
268    if (*val == NULL)
269	return NULL;
270    cur = *val;
271    *val = (*val)->mv_next;
272
273    str = cur->mv_value;
274    free(cur->mv_name);
275    free(cur);
276
277    return str;
278}
279
280static int
281parse_values(const char *string, struct md5_value **val)
282{
283    struct md5_value *v;
284    size_t size;
285    char *str, *p1, *p2;
286    size_t sz;
287
288    *val = NULL;
289
290    if ((str = strdup(string)) == NULL)
291	return ENOMEM;
292
293    size = strlen(str);
294
295    p1 = str;
296
297    while ((size_t)(p1 - str) < size) {
298	sz = strspn(p1, " \t\n\r,");
299	if (p1[sz] == '\0')
300	    break;
301	p1 += sz;
302	sz = strcspn(p1, " \t\n\r=");
303	if (sz == 0 || p1[sz] == '\0')
304	    goto error;
305	p2 = p1 + sz;
306
307	if ((v = malloc(sizeof(*v))) == NULL)
308	    goto nomem;
309	v->mv_name = v->mv_value = NULL;
310	v->mv_next = *val;
311	*val = v;
312	if ((v->mv_name = malloc(p2 - p1 + 1)) == NULL)
313	    goto nomem;
314	strncpy(v->mv_name, p1, p2 - p1);
315	v->mv_name[p2 - p1] = '\0';
316
317	sz = strspn(p2, " \t\n\r");
318	if (p2[sz] == '\0')
319	    goto error;
320	p2 += sz;
321
322	if (*p2 != '=')
323	    goto error;
324	p2++;
325
326	sz = strspn(p2, " \t\n\r");
327	if (p2[sz] == '\0')
328	    goto error;
329	p2 += sz;
330	p1 = p2;
331
332	if (*p2 == '"') {
333	    p1++;
334	    while (*p2 == '"') {
335		p2++;
336		p2 = strchr(p2, '\"');
337		if (p2 == NULL)
338		    goto error;
339		if (p2[0] == '\0')
340		    goto error;
341		if (p2[-1] != '\\')
342		    break;
343	    }
344	} else {
345	    sz = strcspn(p2, " \t\n\r=,");
346	    p2 += sz;
347	}
348
349#if 0 /* allow empty values */
350	if (p1 == p2)
351	    goto error;
352#endif
353
354	if ((v->mv_value = malloc(p2 - p1 + 1)) == NULL)
355	    goto nomem;
356	strncpy(v->mv_value, p1, p2 - p1);
357	v->mv_value[p2 - p1] = '\0';
358
359	if (p2[0] == '\0')
360	    break;
361	if (p2[0] == '"')
362	    p2++;
363
364	sz = strspn(p2, " \t\n\r");
365	if (p2[sz] == '\0')
366	    break;
367	p2 += sz;
368
369	if (p2[0] == '\0')
370	    break;
371	if (p2[0] != ',')
372	    goto error;
373	p1 = p2;
374    }
375
376    free(str);
377
378    return 0;
379 error:
380    free_values(*val);
381    *val = NULL;
382    free(str);
383    return EINVAL;
384 nomem:
385    free_values(*val);
386    *val = NULL;
387    free(str);
388    return ENOMEM;
389}
390
391/*
392 *
393 */
394
395static const char *
396check_prefix(heim_digest_t context, const char *challenge)
397{
398    if (strncasecmp(digest_prefix, challenge, sizeof(digest_prefix) - 1) == 0) {
399
400	challenge += sizeof(digest_prefix) - 1;
401	while (*challenge == 0x20) /* remove extra space */
402	    challenge++;
403	context->flags |= F_USE_PREFIX;
404    }
405
406    return challenge;
407}
408
409/*
410 *
411 */
412
413heim_digest_t
414heim_digest_create(int server, int type)
415{
416    heim_digest_t context;
417
418    context = calloc(1, sizeof(*context));
419    if (context == NULL)
420	return NULL;
421    context->flags |= F_SERVER;
422    context->type = type;
423
424    return context;
425}
426
427static char *
428generate_nonce(void)
429{
430    uint8_t rand[8];
431    char *nonce;
432
433    if (CCRandomCopyBytes(kCCRandomDefault, rand, sizeof(rand)) != kCCSuccess)
434	return NULL;
435
436    if (rk_hex_encode(rand, sizeof(rand), &nonce) < 0)
437	return NULL;
438
439    return nonce;
440}
441
442/**
443 * Generate a challange, needs to set serverRealm before calling this function.
444 *
445 * If type is set to HEIM_DIGEST_TYPE_AUTO, the HEIM_DIGEST_TYPE_RFC2831 will be used instead.
446 *
447 * 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().
448 *
449 * @return returns the challenge or NULL on error or failure to build the string. the lifetime
450 *         of the string is manage by heim_digest and last until the the context is
451 *         freed or until next call to heim_digest_generate_challenge().
452 */
453
454const char *
455heim_digest_generate_challenge(heim_digest_t context)
456{
457    char *challenge = NULL;
458
459    if (context->serverRealm == NULL)
460	return NULL;
461
462    if (context->serverNonce == NULL) {
463	if ((context->serverNonce = generate_nonce()) == NULL)
464	    return NULL;
465    }
466
467    if (context->serverQOP == NULL) {
468	if ((context->serverQOP = strdup("auth")) == NULL)
469	    return NULL;
470    }
471
472    if (context->serverMaxbuf == NULL) {
473	if ((context->serverMaxbuf = strdup("65536")) == NULL)
474	    return NULL;
475    }
476
477    switch(context->type) {
478	case HEIM_DIGEST_TYPE_RFC2617_MD5:
479	    asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5,qop=\"%s\"",
480		     context->serverRealm, context->serverNonce,
481		     context->serverQOP);
482	    break;
483	case HEIM_DIGEST_TYPE_RFC2617_MD5_SESS:
484	    asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5-sess,qop=\"%s\"",
485		     context->serverRealm, context->serverNonce, context->serverQOP);
486	    break;
487	case HEIM_DIGEST_TYPE_RFC2069:
488	    asprintf(&challenge, "realm=\"%s\",nonce=\"%s\"",
489		     context->serverRealm, context->serverNonce);
490	    break;
491	case HEIM_DIGEST_TYPE_AUTO:
492	    context->type = HEIM_DIGEST_TYPE_RFC2831;
493	    /* FALL THOUGH */
494	case HEIM_DIGEST_TYPE_RFC2831:
495	    asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",qop=\"%s\",algorithm=md5-sess,charset=utf-8,maxbuf=%s",
496		     context->serverRealm, context->serverNonce, context->serverQOP, context->serverMaxbuf);
497	    break;
498    }
499
500    FREE_AND_CLEAR(context->serverChallenge);
501    context->serverChallenge = challenge;
502
503    return challenge;
504}
505
506int
507heim_digest_parse_challenge(heim_digest_t context, const char *challenge)
508{
509    struct md5_value *val = NULL;
510    int ret, type;
511
512    challenge = check_prefix(context, challenge);
513
514    ret = parse_values(challenge, &val);
515    if (ret)
516	goto out;
517
518    ret = 1;
519
520    context->serverNonce = values_find(&val, "nonce");
521    if (context->serverNonce == NULL) goto out;
522
523    context->serverRealm = values_find(&val, "realm");
524    if (context->serverRealm == NULL) goto out;
525
526    /* check alg */
527
528    context->serverAlgorithm = values_find(&val, "algorithm");
529    if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) {
530	type = HEIM_DIGEST_TYPE_RFC2617_MD5;
531    } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) {
532	type = HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831;
533    } else {
534	goto out;
535    }
536
537    context->serverQOP = values_find(&val, "qop");
538    if (context->serverQOP == NULL)
539	type = HEIM_DIGEST_TYPE_RFC2069;
540
541    context->serverOpaque = values_find(&val, "opaque");
542
543    if (context->type != HEIM_DIGEST_TYPE_AUTO && (context->type & type) == 0)
544	goto out;
545    else if (context->type == HEIM_DIGEST_TYPE_AUTO)
546	context->type = type;
547
548    ret = 0;
549 out:
550    free_values(val);
551    if (ret)
552	clear_context(context);
553    return ret;
554}
555
556
557static void
558set_auth_method(heim_digest_t context)
559{
560
561    if (context->serverMethod == NULL) {
562	if (context->type == HEIM_DIGEST_TYPE_RFC2831)
563	    context->serverMethod = strdup("AUTHENTICATE");
564	else
565	    context->serverMethod = strdup("GET");
566    }
567}
568
569int
570heim_digest_parse_response(heim_digest_t context, const char *response)
571{
572    struct md5_value *val = NULL;
573    char *nonce;
574    int ret;
575
576    response = check_prefix(context, response);
577
578    ret = parse_values(response, &val);
579    if (ret)
580	goto out;
581
582    ret = 1;
583
584    if (context->type == HEIM_DIGEST_TYPE_AUTO) {
585	goto out;
586    } else if (context->type == HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831) {
587	context->clientURI = values_find(&val, "uri");
588	if (context->clientURI) {
589	    context->type = HEIM_DIGEST_TYPE_RFC2617_MD5_SESS;
590	} else {
591	    context->clientURI = values_find(&val, "digest-uri");
592	    context->type = HEIM_DIGEST_TYPE_RFC2831;
593	}
594    } else if (context->type == HEIM_DIGEST_TYPE_RFC2831) {
595	context->clientURI = values_find(&val, "digest-uri");
596    } else {
597	context->clientURI = values_find(&val, "uri");
598    }
599
600    if (context->clientURI == NULL)
601        goto out;
602
603    context->clientUsername = values_find(&val, "username");
604    if (context->clientUsername == NULL) goto out;
605
606    /* if client sent realm, make sure its the same of serverRealm if its set */
607    context->clientRealm = values_find(&val, "realm");
608    if (context->clientRealm && context->serverRealm && strcmp(context->clientRealm, context->serverRealm) != 0)
609	goto out;
610
611    context->clientResponse = values_find(&val, "response");
612    if (context->clientResponse == NULL) goto out;
613
614    nonce = values_find(&val, "nonce");
615    if (nonce == NULL) goto out;
616
617    if (strcmp(nonce, context->serverNonce) != 0) {
618	free(nonce);
619	goto out;
620    }
621    free(nonce);
622
623    if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
624
625	context->clientQOP = values_find(&val, "qop");
626	if (context->clientQOP == NULL) goto out;
627
628	/*
629	 * If we have serverQOP, lets check that clientQOP exists
630	 * in the list of server entries.
631	 */
632
633	if (context->serverQOP) {
634	    Boolean found = false;
635	    char *b, *e;
636	    size_t len, clen = strlen(context->clientQOP);
637
638	    b = context->serverQOP;
639	    while (b && !found) {
640		e = strchr(b, ',');
641		if (e == NULL)
642		    len = strlen(b);
643		else {
644		    len = e - b;
645		    e += 1;
646		}
647		if (clen == len && strncmp(b, context->clientQOP, len) == 0)
648		    found = true;
649		b = e;
650	    }
651	    if (!found)
652		goto out;
653	}
654
655	context->clientNC = values_find(&val, "nc");
656	if (context->clientNC == NULL) goto out;
657
658	context->clientNonce = values_find(&val, "cnonce");
659	if (context->clientNonce == NULL) goto out;
660    }
661
662    set_auth_method(context);
663
664    ret = 0;
665 out:
666    free_values(val);
667    return ret;
668}
669
670char *
671heim_digest_userhash(const char *user, const char *realm, const char *password)
672{
673    CC_MD5_CTX ctx;
674
675    CC_MD5_Init(&ctx);
676    CC_MD5_Update(&ctx, user, (CC_LONG)strlen(user));
677    CC_MD5_Update(&ctx, ":", 1);
678    CC_MD5_Update(&ctx, realm, (CC_LONG)strlen(realm));
679    CC_MD5_Update(&ctx, ":", 1);
680    CC_MD5_Update(&ctx, password, (CC_LONG)strlen(password));
681
682    return MD5_Final_hex(&ctx);
683}
684
685static char *
686build_digest(heim_digest_t context, const char *a1, const char *method)
687{
688    CC_MD5_CTX ctx;
689    char *a2, *str = NULL;
690
691    a2 = build_A2_hash(context, method);
692    if (a2 == NULL)
693      return NULL;
694
695    CC_MD5_Init(&ctx);
696    CC_MD5_Update(&ctx, a1, (CC_LONG)strlen(a1));
697    CC_MD5_Update(&ctx, ":", 1);
698    CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce));
699    if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
700	CC_MD5_Update(&ctx, ":", 1);
701	CC_MD5_Update(&ctx, context->clientNC, (CC_LONG)strlen(context->clientNC));
702	CC_MD5_Update(&ctx, ":", 1);
703	CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce));
704	CC_MD5_Update(&ctx, ":", 1);
705	CC_MD5_Update(&ctx, context->clientQOP, (CC_LONG)strlen(context->clientQOP));
706    }
707    CC_MD5_Update(&ctx, ":", 1);
708    CC_MD5_Update(&ctx, a2, (CC_LONG)strlen(a2));
709
710    str = MD5_Final_hex(&ctx);
711
712    free(a2);
713
714    return str;
715}
716
717static void
718build_server_response(heim_digest_t context, char *a1, char **response)
719{
720    char *str;
721
722    str = build_digest(context, a1, NULL);
723    if (str == NULL)
724	return;
725
726    FREE_AND_CLEAR(context->serverReply);
727    asprintf(&context->serverReply, "%srspauth=%s",
728	     (context->flags & F_USE_PREFIX) ? digest_prefix : "",
729	     str);
730    free(str);
731    if (response)
732	*response = context->serverReply;
733}
734
735
736/**
737 * Create response from server to client to server, server verification is in response.
738 * clientUsername and clientURI have to be given.
739 * If realm is not set, its used from server.
740 */
741
742const char *
743heim_digest_create_response(heim_digest_t context, char **response)
744{
745    char *a1, *str, *cnonce = NULL, *opaque = NULL, *uri = NULL, *nc = NULL;
746
747    if (response)
748	*response = NULL;
749
750    if (context->clientUsername == NULL || context->clientURI == NULL)
751	return NULL;
752
753    if (context->clientRealm == NULL) {
754	if (context->serverRealm == NULL)
755	    return NULL;
756	if ((context->clientRealm = strdup(context->serverRealm)) == NULL)
757	    return NULL;
758    }
759
760    if (context->type != HEIM_DIGEST_TYPE_RFC2069) {
761	if (context->clientNC == NULL) {
762	    if ((context->clientNC = strdup("00000001")) == NULL)
763		return NULL;
764	}
765	if (context->clientNonce == NULL) {
766	    if ((context->clientNonce = generate_nonce()) == NULL)
767		return NULL;
768	}
769
770	/**
771	 * If using non RFC2069, appropriate QOP should be set.
772	 *
773	 * Pick QOP from server if not given, if its a list, pick the first entry
774	 */
775	if (context->clientQOP == NULL) {
776	    char *r;
777	    if (context->serverQOP == NULL)
778		return NULL;
779	    r = strchr(context->serverQOP, ',');
780	    if (r == NULL) {
781		if ((context->clientQOP = strdup(context->serverQOP)) == NULL)
782			return NULL;
783	    } else {
784		size_t len = (r - context->serverQOP) + 1;
785		if ((context->clientQOP = malloc(len)) == NULL)
786		    return NULL;
787		strlcpy(context->clientQOP, context->serverQOP, len);
788	    }
789	}
790    }
791
792    set_auth_method(context);
793
794    a1 = build_A1_hash(context, 0);
795    if (a1 == NULL)
796	return NULL;
797
798    str = build_digest(context, a1, context->serverMethod);
799    if (str == NULL) {
800	MEMSET_FREE_AND_CLEAR(a1);
801	return NULL;
802    }
803
804    MEMSET_FREE_AND_CLEAR(context->clientResponse);
805    context->clientResponse = str;
806
807    if (context->clientURI) {
808	const char *name = "digest-uri";
809	if (context->type != HEIM_DIGEST_TYPE_RFC2831)
810	    name = "uri";
811	asprintf(&uri, ",%s=\"%s\"", name, context->clientURI);
812    }
813
814    if (context->serverOpaque)
815	asprintf(&opaque, ",opaque=\"%s\"", context->serverOpaque);
816
817    if (context->clientNonce)
818	asprintf(&cnonce, ",cnonce=\"%s\"", context->clientNonce);
819
820    if (context->clientNC)
821	asprintf(&nc, ",nc=%s", context->clientNC);
822
823    asprintf(&context->clientReply,
824	     "username=%s,realm=%s,nonce=\"%s\",qop=\"%s\"%s%s%s,response=\"%s\"%s",
825	     context->clientUsername, context->clientRealm,
826	     context->serverNonce,
827	     context->clientQOP,
828	     uri ? uri : "",
829	     cnonce ? cnonce : "",
830	     nc ? nc : "",
831	     context->clientResponse,
832	     opaque ? opaque : "");
833
834    build_server_response(context, a1, response);
835    MEMSET_FREE_AND_CLEAR(a1);
836    FREE_AND_CLEAR(uri);
837    FREE_AND_CLEAR(opaque);
838    FREE_AND_CLEAR(cnonce);
839    FREE_AND_CLEAR(nc);
840
841    return context->clientReply;
842}
843
844int
845heim_digest_verify(heim_digest_t context, char **response)
846{
847    char *a1;
848    char *str;
849    int res;
850
851    if (response)
852	*response = NULL;
853
854    set_auth_method(context);
855
856    a1 = build_A1_hash(context, 0);
857    if (a1 == NULL)
858	return ENOMEM;
859
860    str = build_digest(context, a1, context->serverMethod);
861    if (str == NULL) {
862	MEMSET_FREE_AND_CLEAR(a1);
863	return ENOMEM;
864    }
865
866    res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL;
867    free(str);
868    if (res) {
869	MEMSET_FREE_AND_CLEAR(a1);
870
871	/*
872	 * We got the inner hash wrong for (http) md5-sess so lets
873	 * check that too before punting.
874	 */
875	if (context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS) {
876	    a1 = build_A1_hash(context, 1);
877	    if (a1 == NULL)
878		return ENOMEM;
879
880	    str = build_digest(context, a1, context->serverMethod);
881	    if (str == NULL) {
882		MEMSET_FREE_AND_CLEAR(a1);
883		return ENOMEM;
884	    }
885
886	    res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL;
887	    free(str);
888	    if (res) {
889		MEMSET_FREE_AND_CLEAR(a1);
890		return res;
891	    }
892
893	    /* backward compat method worked */
894
895	} else {
896	    MEMSET_FREE_AND_CLEAR(a1);
897	    return res;
898	}
899    }
900
901    /* build server_response */
902    build_server_response(context, a1, response);
903    MEMSET_FREE_AND_CLEAR(a1);
904    /* XXX break ABI and return internally allocated string instead */
905    if (response)
906	*response = strdup(*response);
907
908    return 0;
909}
910
911/**
912 * Create a rspauth= response.
913 * Assumes that the A1hash/password serverNonce, clientNC, clientNonce, clientQOP is set.
914 *
915 * @return the rspauth string (including rspauth), return key are stored in serverReply and will be invalid after another call to heim_digest_*
916 */
917
918const char *
919heim_digest_server_response(heim_digest_t context)
920{
921    char *a1;
922
923    if (context->serverNonce == NULL)
924	return NULL;
925    if (context->clientURI == NULL)
926	return NULL;
927
928    a1 = build_A1_hash(context, 0);
929    if (a1 == NULL)
930	return NULL;
931
932    build_server_response(context, a1, NULL);
933    MEMSET_FREE_AND_CLEAR(a1);
934
935    return context->serverReply;
936}
937
938void
939heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize)
940{
941}
942
943void
944heim_digest_release(heim_digest_t context)
945{
946    clear_context(context);
947    free(context);
948}
949
950struct {
951    char *name;
952    size_t offset;
953} keys[] = {
954#define KVN(value) { #value, offsetof(struct heim_digest_desc, value) }
955    KVN(serverNonce),
956    KVN(serverRealm),
957    KVN(serverQOP),
958    KVN(serverMethod),
959    { "method", offsetof(struct heim_digest_desc, serverMethod) },
960    KVN(serverMaxbuf),
961    KVN(clientUsername),
962    { "username", offsetof(struct heim_digest_desc, clientUsername) },
963    KVN(clientResponse),
964    KVN(clientURI),
965    { "uri", offsetof(struct heim_digest_desc, clientURI) },
966    KVN(clientRealm),
967    { "realm", offsetof(struct heim_digest_desc, clientRealm) },
968    KVN(clientNonce),
969    KVN(clientQOP),
970    KVN(clientNC),
971    KVN(serverAlgorithm),
972    KVN(auth_id)
973#undef KVN
974};
975
976const char *
977heim_digest_get_key(heim_digest_t context, const char *key)
978{
979    size_t n;
980
981    for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) {
982	if (strcasecmp(key, keys[n].name) == 0) {
983	    char **ptr = (char **)((((char *)context) + keys[n].offset));
984	    return *ptr;
985	}
986    }
987    return NULL;
988}
989
990int
991heim_digest_set_key(heim_digest_t context, const char *key, const char *value)
992{
993
994    if (strcmp(key, "password") == 0) {
995
996	FREE_AND_CLEAR(context->password);
997	if ((context->password = strdup(value)) == NULL)
998	    return ENOMEM;
999	context->flags &= ~(F_HAVE_HASH);
1000
1001    } else if (strcmp(key, "userhash") == 0 || strcmp(key, "H(A1)") == 0) {
1002
1003	FREE_AND_CLEAR(context->password);
1004
1005	if (strlen(value) != CC_MD5_DIGEST_LENGTH * 2)
1006	    return ENOMEM;
1007
1008	if ((context->secretHash = strdup(value)) == NULL)
1009	    return ENOMEM;
1010
1011	if (strcmp(key, "userhash") == 0) {
1012	    context->flags |= F_HAVE_HASH;
1013	    context->flags &= ~F_HAVE_HA1;
1014	} else {
1015	    context->flags |= F_HAVE_HA1;
1016	    context->flags &= ~F_HAVE_HASH;
1017	}
1018    } else if (strcmp(key, "method") == 0) {
1019	FREE_AND_CLEAR(context->serverMethod);
1020	if ((context->serverMethod = strdup(value)) == NULL)
1021	    return ENOMEM;
1022    } else {
1023	size_t n;
1024
1025	for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) {
1026	    if (strcasecmp(key, keys[n].name) == 0) {
1027		char **ptr = (char **)((((char *)context) + keys[n].offset));
1028		FREE_AND_CLEAR(*ptr);
1029		if (((*ptr) = strdup(value)) == NULL)
1030		    return ENOMEM;
1031		break;
1032	    }
1033	}
1034	if (n == sizeof(keys) / sizeof(keys[0]))
1035	    return ENOENT;
1036    }
1037    return 0;
1038}
1039
1040