diff options
Diffstat (limited to 'third_party/heimdal/lib/ntlm/digest.c')
-rw-r--r-- | third_party/heimdal/lib/ntlm/digest.c | 994 |
1 files changed, 994 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/ntlm/digest.c b/third_party/heimdal/lib/ntlm/digest.c new file mode 100644 index 0000000..761e1f4 --- /dev/null +++ b/third_party/heimdal/lib/ntlm/digest.c @@ -0,0 +1,994 @@ +/* + * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <CommonCrypto/CommonDigest.h> +#include <CommonCrypto/CommonHMAC.h> +#include <assert.h> +#include <roken.h> +#include <hex.h> +#include "heim-auth.h" +#include "ntlm_err.h" + +struct heim_digest_desc { +#define F_SERVER 1 +#define F_HAVE_HASH 2 +#define F_HAVE_HA1 4 +#define F_USE_PREFIX 8 + int flags; + int type; + char *password; + uint8_t SecretHash[CC_MD5_DIGEST_LENGTH]; + char *serverNonce; + char *serverRealm; + char *serverQOP; + char *serverMethod; + char *serverMaxbuf; + char *serverOpaque; + char *clientUsername; + char *clientResponse; + char *clientURI; + char *clientRealm; + char *clientNonce; + char *clientQOP; + char *clientNC; + char *serverAlgorithm; + char *auth_id; + + /* internally allocated objects returned to caller */ + char *serverChallenge; + char *clientReply; + char *serverReply; +}; + +#define FREE_AND_CLEAR(x) do { if ((x)) { free((x)); (x) = NULL; } } while(0) +#define MEMSET_FREE_AND_CLEAR(x) do { if ((x)) { memset(x, 0, strlen(x)); free((x)); (x) = NULL; } } while(0) + +static const char digest_prefix[] = "Digest "; + +static void +clear_context(heim_digest_t context) +{ + MEMSET_FREE_AND_CLEAR(context->password); + memset(context->SecretHash, 0, sizeof(context->SecretHash)); + context->flags &= ~(F_HAVE_HASH); + FREE_AND_CLEAR(context->serverNonce); + FREE_AND_CLEAR(context->serverRealm); + FREE_AND_CLEAR(context->serverQOP); + FREE_AND_CLEAR(context->serverMethod); + FREE_AND_CLEAR(context->serverMaxbuf); + FREE_AND_CLEAR(context->serverOpaque); + FREE_AND_CLEAR(context->clientUsername); + FREE_AND_CLEAR(context->clientResponse); + FREE_AND_CLEAR(context->clientURI); + FREE_AND_CLEAR(context->clientRealm); + FREE_AND_CLEAR(context->clientNonce); + FREE_AND_CLEAR(context->clientQOP); + FREE_AND_CLEAR(context->clientNC); + FREE_AND_CLEAR(context->serverAlgorithm); + FREE_AND_CLEAR(context->auth_id); + + FREE_AND_CLEAR(context->serverChallenge); + FREE_AND_CLEAR(context->clientReply); + FREE_AND_CLEAR(context->serverReply); +} + +static void +digest_userhash(const char *user, const char *realm, const char *password, + unsigned char md[CC_MD5_DIGEST_LENGTH]) +{ + CC_MD5_CTX ctx; + + CC_MD5_Init(&ctx); + CC_MD5_Update(&ctx, user, (CC_LONG)strlen(user)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, realm, (CC_LONG)strlen(realm)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, password, (CC_LONG)strlen(password)); + CC_MD5_Final(md, &ctx); +} + +static char * +build_A1_hash(heim_digest_t context) +{ + unsigned char md[CC_MD5_DIGEST_LENGTH]; + CC_MD5_CTX ctx; + char *A1; + + if (context->flags & F_HAVE_HA1) { + memcpy(md, context->SecretHash, sizeof(md)); + } else if (context->flags & F_HAVE_HASH) { + memcpy(md, context->SecretHash, sizeof(md)); + } else if (context->password) { + if (context->clientUsername == NULL) + return NULL; + if (context->serverRealm == NULL) + return NULL; + digest_userhash(context->clientUsername, + context->serverRealm, + context->password, + md); + } else + return NULL; + + if ((context->type == HEIM_DIGEST_TYPE_RFC2617_MD5_SESS || context->type == HEIM_DIGEST_TYPE_RFC2831) && (context->flags & F_HAVE_HA1) == 0) { + if (context->serverNonce == NULL) + return NULL; + + CC_MD5_Init(&ctx); + CC_MD5_Update(&ctx, md, sizeof(md)); + memset(md, 0, sizeof(md)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce)); + if (context->clientNonce) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce)); + } + if (context->type == HEIM_DIGEST_TYPE_RFC2831 && context->auth_id) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->auth_id, (CC_LONG)strlen(context->auth_id)); + } + CC_MD5_Final(md, &ctx); + } + hex_encode(md, sizeof(md), &A1); + if (A1) + strlwr(A1); + + return A1; +} + +static char * +build_A2_hash(heim_digest_t context, const char *method) +{ + unsigned char md[CC_MD5_DIGEST_LENGTH]; + CC_MD5_CTX ctx; + char *A2; + + CC_MD5_Init(&ctx); + if (method) + CC_MD5_Update(&ctx, method, (CC_LONG)strlen(method)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientURI, (CC_LONG)strlen(context->clientURI)); + + /* conf|int */ + if (context->type == HEIM_DIGEST_TYPE_RFC2831) { + if (strcasecmp(context->clientQOP, "auth-int") == 0 || strcasecmp(context->clientQOP, "auth-conf") == 0) { + /* XXX if we have a body hash, use that */ + static char conf_zeros[] = ":00000000000000000000000000000000"; + CC_MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1); + } + } else { + /* support auth-int ? */ + if (context->clientQOP && strcasecmp(context->clientQOP, "auth") != 0) + return NULL; + } + + CC_MD5_Final(md, &ctx); + + hex_encode(md, sizeof(md), &A2); + if (A2) + strlwr(A2); + + return A2; +} + +/* + * + */ + +struct md5_value { + char *mv_name; + char *mv_value; + struct md5_value *mv_next; +}; + +static void +free_values(struct md5_value *val) +{ + struct md5_value *v; + while(val) { + v = val->mv_next; + if (val->mv_name) + free(val->mv_name); + if (val->mv_value) + free(val->mv_value); + free(val); + val = v; + } +} + +/* + * Search for entry, if found, remove entry and return string to be freed. + */ + +static char * +values_find(struct md5_value **val, const char *v) +{ + struct md5_value *cur; + char *str; + + while (*val != NULL) { + if (strcasecmp(v, (*val)->mv_name) == 0) + break; + val = &(*val)->mv_next; + } + if (*val == NULL) + return NULL; + cur = *val; + *val = (*val)->mv_next; + + str = cur->mv_value; + free(cur->mv_name); + free(cur); + + return str; +} + +static int +parse_values(const char *string, struct md5_value **val) +{ + struct md5_value *v; + size_t size; + char *str, *p1, *p2; + size_t sz; + + *val = NULL; + + if ((str = strdup(string)) == NULL) + return ENOMEM; + + size = strlen(str); + + p1 = str; + + while (p1 - str < size) { + sz = strspn(p1, " \t\n\r,"); + if (p1[sz] == '\0') + break; + p1 += sz; + sz = strcspn(p1, " \t\n\r="); + if (sz == 0 || p1[sz] == '\0') + goto error; + p2 = p1 + sz; + + if ((v = malloc(sizeof(*v))) == NULL) + goto nomem; + v->mv_name = v->mv_value = NULL; + v->mv_next = *val; + *val = v; + if ((v->mv_name = malloc(p2 - p1 + 1)) == NULL) + goto nomem; + strncpy(v->mv_name, p1, p2 - p1); + v->mv_name[p2 - p1] = '\0'; + + sz = strspn(p2, " \t\n\r"); + if (p2[sz] == '\0') + goto error; + p2 += sz; + + if (*p2 != '=') + goto error; + p2++; + + sz = strspn(p2, " \t\n\r"); + if (p2[sz] == '\0') + goto error; + p2 += sz; + p1 = p2; + + if (*p2 == '"') { + p1++; + while (*p2 == '"') { + p2++; + p2 = strchr(p2, '\"'); + if (p2 == NULL) + goto error; + if (p2[0] == '\0') + goto error; + if (p2[-1] != '\\') + break; + } + } else { + sz = strcspn(p2, " \t\n\r=,"); + p2 += sz; + } + +#if 0 /* allow empty values */ + if (p1 == p2) + goto error; +#endif + + if ((v->mv_value = malloc(p2 - p1 + 1)) == NULL) + goto nomem; + strncpy(v->mv_value, p1, p2 - p1); + v->mv_value[p2 - p1] = '\0'; + + if (p2[0] == '\0') + break; + if (p2[0] == '"') + p2++; + + sz = strspn(p2, " \t\n\r"); + if (p2[sz] == '\0') + break; + p2 += sz; + + if (p2[0] == '\0') + break; + if (p2[0] != ',') + goto error; + p1 = p2; + } + + free(str); + + return 0; + error: + free_values(*val); + *val = NULL; + free(str); + return EINVAL; + nomem: + free_values(*val); + *val = NULL; + free(str); + return ENOMEM; +} + +/* + * + */ + +static const char * +check_prefix(heim_digest_t context, const char *challenge) +{ + if (strncasecmp(digest_prefix, challenge, sizeof(digest_prefix) - 1) == 0) { + + challenge += sizeof(digest_prefix) - 1; + while (*challenge == 0x20) /* remove extra space */ + challenge++; + context->flags |= F_USE_PREFIX; + } + + return challenge; +} + +/* + * + */ + +heim_digest_t +heim_digest_create(int server, int type) +{ + heim_digest_t context; + + context = calloc(1, sizeof(*context)); + if (context == NULL) + return NULL; + context->flags |= F_SERVER; + context->type = type; + + return context; +} + +static char * +generate_nonce(void) +{ + uint8_t rand[8]; + char *nonce; + + if (CCRandomCopyBytes(kCCRandomDefault, rand, sizeof(rand)) != kCCSuccess) + return NULL; + + if (rk_hex_encode(rand, sizeof(rand), &nonce) < 0) + return NULL; + + return nonce; +} + +/** + * Generate a challange, needs to set serverRealm before calling this function. + * + * If type is set to HEIM_DIGEST_TYPE_AUTO, the HEIM_DIGEST_TYPE_RFC2831 will be used instead. + * + * 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(). + * + * @return returns the challenge or NULL on error or failure to build the string. the lifetime + * of the string is manage by heim_digest and last until the the context is + * freed or until next call to heim_digest_generate_challenge(). + */ + +const char * +heim_digest_generate_challenge(heim_digest_t context) +{ + char *challenge = NULL; + + if (context->serverRealm == NULL) + return NULL; + + if (context->serverNonce == NULL) { + if ((context->serverNonce = generate_nonce()) == NULL) + return NULL; + } + + if (context->serverQOP == NULL) { + if ((context->serverQOP = strdup("auth")) == NULL) + return NULL; + } + + if (context->serverMaxbuf == NULL) { + if ((context->serverMaxbuf = strdup("65536")) == NULL) + return NULL; + } + + switch(context->type) { + case HEIM_DIGEST_TYPE_RFC2617_MD5: + asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5,qop=\"%s\"", + context->serverRealm, context->serverNonce, + context->serverQOP); + break; + case HEIM_DIGEST_TYPE_RFC2617_MD5_SESS: + asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",algorithm=md5-sess,qop=\"%s\"", + context->serverRealm, context->serverNonce, context->serverQOP); + break; + case HEIM_DIGEST_TYPE_RFC2069: + asprintf(&challenge, "realm=\"%s\",nonce=\"%s\"", + context->serverRealm, context->serverNonce); + break; + case HEIM_DIGEST_TYPE_AUTO: + context->type = HEIM_DIGEST_TYPE_RFC2831; + fallthrough; + case HEIM_DIGEST_TYPE_RFC2831: + asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",qop=\"%s\",algorithm=md5-sess,charset=utf-8,maxbuf=%s", + context->serverRealm, context->serverNonce, context->serverQOP, context->serverMaxbuf); + break; + } + + FREE_AND_CLEAR(context->serverChallenge); + context->serverChallenge = challenge; + + return challenge; +} + +int +heim_digest_parse_challenge(heim_digest_t context, const char *challenge) +{ + struct md5_value *val = NULL; + int ret, type; + + challenge = check_prefix(context, challenge); + + ret = parse_values(challenge, &val); + if (ret) + goto out; + + ret = 1; + + context->serverNonce = values_find(&val, "nonce"); + if (context->serverNonce == NULL) goto out; + + context->serverRealm = values_find(&val, "realm"); + if (context->serverRealm == NULL) goto out; + + /* check alg */ + + context->serverAlgorithm = values_find(&val, "algorithm"); + if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) { + type = HEIM_DIGEST_TYPE_RFC2617_MD5; + } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) { + type = HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831; + } else { + goto out; + } + + context->serverQOP = values_find(&val, "qop"); + if (context->serverQOP == NULL) + type = HEIM_DIGEST_TYPE_RFC2069; + + context->serverOpaque = values_find(&val, "opaque"); + + if (context->type != HEIM_DIGEST_TYPE_AUTO && (context->type & type) == 0) + goto out; + else if (context->type == HEIM_DIGEST_TYPE_AUTO) + context->type = type; + + ret = 0; + out: + free_values(val); + if (ret) + clear_context(context); + return ret; +} + + +static void +set_auth_method(heim_digest_t context) +{ + + if (context->serverMethod == NULL) { + if (context->type == HEIM_DIGEST_TYPE_RFC2831) + context->serverMethod = strdup("AUTHENTICATE"); + else + context->serverMethod = strdup("GET"); + } +} + +int +heim_digest_parse_response(heim_digest_t context, const char *response) +{ + struct md5_value *val = NULL; + char *nonce; + int ret; + + response = check_prefix(context, response); + + ret = parse_values(response, &val); + if (ret) + goto out; + + ret = 1; + + if (context->type == HEIM_DIGEST_TYPE_AUTO) { + goto out; + } else if (context->type == HEIM_DIGEST_TYPE_RFC2617_OR_RFC2831) { + context->clientURI = values_find(&val, "uri"); + if (context->clientURI) { + context->type = HEIM_DIGEST_TYPE_RFC2617_MD5_SESS; + } else { + context->clientURI = values_find(&val, "digest-uri"); + context->type = HEIM_DIGEST_TYPE_RFC2831; + } + } else if (context->type == HEIM_DIGEST_TYPE_RFC2831) { + context->clientURI = values_find(&val, "digest-uri"); + } else { + context->clientURI = values_find(&val, "uri"); + } + + if (context->clientURI == NULL) + goto out; + + context->clientUsername = values_find(&val, "username"); + if (context->clientUsername == NULL) goto out; + + /* if client sent realm, make sure its the same of serverRealm if its set */ + context->clientRealm = values_find(&val, "realm"); + if (context->clientRealm && context->serverRealm && strcmp(context->clientRealm, context->serverRealm) != 0) + goto out; + + context->clientResponse = values_find(&val, "response"); + if (context->clientResponse == NULL) goto out; + + nonce = values_find(&val, "nonce"); + if (nonce == NULL) goto out; + + if (strcmp(nonce, context->serverNonce) != 0) { + free(nonce); + goto out; + } + free(nonce); + + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + + context->clientQOP = values_find(&val, "qop"); + if (context->clientQOP == NULL) goto out; + + /* + * If we have serverQOP, lets check that clientQOP exists + * in the list of server entries. + */ + + if (context->serverQOP) { + Boolean found = false; + char *b, *e; + size_t len, clen = strlen(context->clientQOP); + + b = context->serverQOP; + while (b && !found) { + e = strchr(b, ','); + if (e == NULL) + len = strlen(b); + else { + len = e - b; + e += 1; + } + if (clen == len && strncmp(b, context->clientQOP, len) == 0) + found = true; + b = e; + } + if (!found) + goto out; + } + + context->clientNC = values_find(&val, "nc"); + if (context->clientNC == NULL) goto out; + + context->clientNonce = values_find(&val, "cnonce"); + if (context->clientNonce == NULL) goto out; + } + + set_auth_method(context); + + ret = 0; + out: + free_values(val); + return ret; +} + +char * +heim_digest_userhash(const char *user, const char *realm, const char *password) +{ + unsigned char md[CC_MD5_DIGEST_LENGTH]; + char *str = NULL; + + digest_userhash(user, realm, password, md); + + hex_encode(md, sizeof(md), &str); + if (str) + strlwr(str); + return str; +} + +static char * +build_digest(heim_digest_t context, const char *a1, const char *method) +{ + CC_MD5_CTX ctx; + uint8_t md[CC_MD5_DIGEST_LENGTH]; + char *a2, *str = NULL; + + a2 = build_A2_hash(context, method); + if (a2 == NULL) + return NULL; + + CC_MD5_Init(&ctx); + CC_MD5_Update(&ctx, a1, (CC_LONG)strlen(a1)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->serverNonce, (CC_LONG)strlen(context->serverNonce)); + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientNC, (CC_LONG)strlen(context->clientNC)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientNonce, (CC_LONG)strlen(context->clientNonce)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientQOP, (CC_LONG)strlen(context->clientQOP)); + } + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, a2, (CC_LONG)strlen(a2)); + CC_MD5_Final(md, &ctx); + + free(a2); + + hex_encode(md, sizeof(md), &str); + if (str) + strlwr(str); + + return str; +} + +static void +build_server_response(heim_digest_t context, char *a1, char **response) +{ + char *str; + + str = build_digest(context, a1, NULL); + if (str == NULL) + return; + + FREE_AND_CLEAR(context->serverReply); + asprintf(&context->serverReply, "%srspauth=%s", + (context->flags & F_USE_PREFIX) ? digest_prefix : "", + str); + free(str); + if (response) + *response = context->serverReply; +} + + +/** + * Create response from server to client to server, server verification is in response. + * clientUsername and clientURI have to be given. + * If realm is not set, its used from server. + */ + +const char * +heim_digest_create_response(heim_digest_t context, char **response) +{ + char *a1, *str, *cnonce = NULL, *opaque = NULL, *uri = NULL, *nc = NULL; + + if (response) + *response = NULL; + + if (context->clientUsername == NULL || context->clientURI == NULL) + return NULL; + + if (context->clientRealm == NULL) { + if (context->serverRealm == NULL) + return NULL; + if ((context->clientRealm = strdup(context->serverRealm)) == NULL) + return NULL; + } + + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + if (context->clientNC == NULL) { + if ((context->clientNC = strdup("00000001")) == NULL) + return NULL; + } + if (context->clientNonce == NULL) { + if ((context->clientNonce = generate_nonce()) == NULL) + return NULL; + } + + /** + * If using non RFC2069, appropriate QOP should be set. + * + * Pick QOP from server if not given, if its a list, pick the first entry + */ + if (context->clientQOP == NULL) { + char *r; + if (context->serverQOP == NULL) + return NULL; + r = strchr(context->serverQOP, ','); + if (r == NULL) { + if ((context->clientQOP = strdup(context->serverQOP)) == NULL) + return NULL; + } else { + size_t len = (r - context->serverQOP) + 1; + if ((context->clientQOP = malloc(len)) == NULL) + return NULL; + strlcpy(context->clientQOP, context->serverQOP, len); + } + } + } + + set_auth_method(context); + + a1 = build_A1_hash(context); + if (a1 == NULL) + return NULL; + + str = build_digest(context, a1, context->serverMethod); + if (str == NULL) { + MEMSET_FREE_AND_CLEAR(a1); + return NULL; + } + + MEMSET_FREE_AND_CLEAR(context->clientResponse); + context->clientResponse = str; + + if (context->clientURI) { + const char *name = "digest-uri"; + if (context->type != HEIM_DIGEST_TYPE_RFC2831) + name = "uri"; + asprintf(&uri, ",%s=\"%s\"", name, context->clientURI); + } + + if (context->serverOpaque) + asprintf(&opaque, ",opaque=\"%s\"", context->serverOpaque); + + if (context->clientNonce) + asprintf(&cnonce, ",cnonce=\"%s\"", context->clientNonce); + + if (context->clientNC) + asprintf(&nc, ",nc=%s", context->clientNC); + + asprintf(&context->clientReply, + "username=%s,realm=%s,nonce=\"%s\",qop=\"%s\"%s%s%s,response=\"%s\"%s", + context->clientUsername, context->clientRealm, + context->serverNonce, + context->clientQOP, + uri ? uri : "", + cnonce ? cnonce : "", + nc ? nc : "", + context->clientResponse, + opaque ? opaque : ""); + + build_server_response(context, a1, response); + MEMSET_FREE_AND_CLEAR(a1); + FREE_AND_CLEAR(uri); + FREE_AND_CLEAR(opaque); + FREE_AND_CLEAR(cnonce); + FREE_AND_CLEAR(nc); + + return context->clientReply; +} + +int +heim_digest_verify(heim_digest_t context, char **response) +{ + char *a1; + char *str; + int res; + + if (response) + *response = NULL; + + set_auth_method(context); + + a1 = build_A1_hash(context); + if (a1 == NULL) + return ENOMEM; + + str = build_digest(context, a1, context->serverMethod); + if (str == NULL) { + MEMSET_FREE_AND_CLEAR(a1); + return ENOMEM; + } + + res = (strcmp(str, context->clientResponse) == 0) ? 0 : EINVAL; + free(str); + if (res) { + MEMSET_FREE_AND_CLEAR(a1); + return res; + } + + /* build server_response */ + build_server_response(context, a1, response); + MEMSET_FREE_AND_CLEAR(a1); + /* XXX break ABI and return internally allocated string instead */ + if (response) + *response = strdup(*response); + + return 0; +} + +/** + * Create a rspauth= response. + * Assumes that the A1hash/password serverNonce, clientNC, clientNonce, clientQOP is set. + * + * @return the rspauth string (including rspauth), return key are stored in serverReply and will be invalid after another call to heim_digest_* + */ + +const char * +heim_digest_server_response(heim_digest_t context) +{ + char *a1; + + if (context->serverNonce == NULL) + return NULL; + if (context->clientURI == NULL) + return NULL; + + a1 = build_A1_hash(context); + if (a1 == NULL) + return NULL; + + build_server_response(context, a1, NULL); + MEMSET_FREE_AND_CLEAR(a1); + + return context->serverReply; +} + +void +heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize) +{ +} + +void +heim_digest_release(heim_digest_t context) +{ + clear_context(context); + free(context); +} + +struct { + char *name; + size_t offset; +} keys[] = { +#define KVN(value) { #value, offsetof(struct heim_digest_desc, value) } + KVN(serverNonce), + KVN(serverRealm), + KVN(serverQOP), + KVN(serverMethod), + { "method", offsetof(struct heim_digest_desc, serverMethod) }, + KVN(serverMaxbuf), + KVN(clientUsername), + { "username", offsetof(struct heim_digest_desc, clientUsername) }, + KVN(clientResponse), + KVN(clientURI), + { "uri", offsetof(struct heim_digest_desc, clientURI) }, + KVN(clientRealm), + { "realm", offsetof(struct heim_digest_desc, clientRealm) }, + KVN(clientNonce), + KVN(clientQOP), + KVN(clientNC), + KVN(serverAlgorithm), + KVN(auth_id) +#undef KVN +}; + +const char * +heim_digest_get_key(heim_digest_t context, const char *key) +{ + size_t n; + + for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) { + if (strcasecmp(key, keys[n].name) == 0) { + char **ptr = (char **)((((char *)context) + keys[n].offset)); + return *ptr; + } + } + return NULL; +} + +int +heim_digest_set_key(heim_digest_t context, const char *key, const char *value) +{ + + if (strcmp(key, "password") == 0) { + FREE_AND_CLEAR(context->password); + if ((context->password = strdup(value)) == NULL) + return ENOMEM; + context->flags &= ~(F_HAVE_HASH|F_HAVE_HA1); + } else if (strcmp(key, "userhash") == 0) { + ssize_t ret; + FREE_AND_CLEAR(context->password); + + ret = hex_decode(value, context->SecretHash, sizeof(context->SecretHash)); + if (ret != sizeof(context->SecretHash)) + return EINVAL; + context->flags &= ~F_HAVE_HA1; + context->flags |= F_HAVE_HASH; + } else if (strcmp(key, "H(A1)") == 0) { + ssize_t ret; + FREE_AND_CLEAR(context->password); + + ret = hex_decode(value, context->SecretHash, sizeof(context->SecretHash)); + if (ret != sizeof(context->SecretHash)) + return EINVAL; + context->flags &= ~F_HAVE_HASH; + context->flags |= F_HAVE_HA1; + } else if (strcmp(key, "method") == 0) { + FREE_AND_CLEAR(context->serverMethod); + if ((context->serverMethod = strdup(value)) == NULL) + return ENOMEM; + } else { + size_t n; + + for (n = 0; n < sizeof(keys) / sizeof(keys[0]); n++) { + if (strcasecmp(key, keys[n].name) == 0) { + char **ptr = (char **)((((char *)context) + keys[n].offset)); + FREE_AND_CLEAR(*ptr); + if (((*ptr) = strdup(value)) == NULL) + return ENOMEM; + break; + } + } + if (n == sizeof(keys) / sizeof(keys[0])) + return ENOENT; + } + return 0; +} + |