From 2a9d00dd917dc76ea45b67439c43199bce85f630 Mon Sep 17 00:00:00 2001 From: Love Hornquist Astrand Date: Tue, 19 Oct 2010 18:16:49 -0700 Subject: [PATCH] add digest and apop support --- lib/ntlm/apop.c | 264 ++++++++++++++++ lib/ntlm/digest.c | 597 +++++++++++++++++++++++++++++++++++++ lib/ntlm/heim-auth.h | 114 +++++++ lib/ntlm/test_commonauth.c | 216 ++++++++++++++ 4 files changed, 1191 insertions(+) create mode 100644 lib/ntlm/apop.c create mode 100644 lib/ntlm/digest.c create mode 100644 lib/ntlm/heim-auth.h create mode 100644 lib/ntlm/test_commonauth.c diff --git a/lib/ntlm/apop.c b/lib/ntlm/apop.c new file mode 100644 index 000000000..9551e0785 --- /dev/null +++ b/lib/ntlm/apop.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2010 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 +#include +#include +#include +#include +#include +#include +#include "heim-auth.h" +#include "ntlm_err.h" + +char * +heim_generate_challenge(const char *hostname) +{ + char host[MAXHOSTNAMELEN], *str = NULL; + uint32_t num, t; + + if (hostname == NULL) { + if (gethostname(host, sizeof(host))) + return NULL; + hostname = host; + } + + t = time(NULL); + num = rk_random(); + + asprintf(&str, "<%lu%lu@%s>", (unsigned long)t, + (unsigned long)num, hostname); + + return str; +} + +char * +heim_apop_create(const char *challenge, const char *password) +{ + char *str = NULL; + uint8_t hash[CC_MD5_DIGEST_LENGTH]; + CC_MD5_CTX ctx; + + CC_MD5_Init(&ctx); + CC_MD5_Update(&ctx, challenge, strlen(challenge)); + CC_MD5_Update(&ctx, password, strlen(password)); + + CC_MD5_Final(hash, &ctx); + + hex_encode(hash, sizeof(hash), &str); + if (str) + strlwr(str); + + return str; +} + +int +heim_apop_verify(const char *challenge, const char *password, const char *response) +{ + char *str; + int res; + + str = heim_apop_create(challenge, password); + if (str == NULL) + return ENOMEM; + + res = (strcasecmp(str, response) != 0); + free(str); + + if (res) + return HNTLM_ERR_INVALID_APOP; + return 0; +} + +struct heim_cram_md5 { + CC_MD5_CTX ipad; + CC_MD5_CTX opad; +}; + + +void +heim_cram_md5_export(const char *password, heim_CRAM_MD5_STATE *state) +{ + size_t keylen = strlen(password); + uint8_t key[CC_MD5_BLOCK_BYTES]; + uint8_t pad[CC_MD5_BLOCK_BYTES]; + struct heim_cram_md5 ctx; + size_t n; + + memset(&ctx, 0, sizeof(ctx)); + + if (keylen > CC_MD5_BLOCK_BYTES) { + CC_MD5(password, keylen, key); + keylen = sizeof(keylen); + } else { + memcpy(key, password, keylen); + } + + memset(pad, 0x36, sizeof(pad)); + for (n = 0; n < keylen; n++) + pad[n] ^= key[n]; + + CC_MD5_Init(&ctx.ipad); + CC_MD5_Init(&ctx.opad); + + CC_MD5_Update(&ctx.ipad, pad, sizeof(pad)); + + memset(pad, 0x5c, sizeof(pad)); + for (n = 0; n < keylen; n++) + pad[n] ^= key[n]; + + CC_MD5_Update(&ctx.opad, pad, sizeof(pad)); + + memset(pad, 0, sizeof(pad)); + memset(key, 0, sizeof(key)); + + state->istate[0] = htonl(ctx.ipad.A); + state->istate[1] = htonl(ctx.ipad.B); + state->istate[2] = htonl(ctx.ipad.C); + state->istate[3] = htonl(ctx.ipad.D); + + state->ostate[0] = htonl(ctx.opad.A); + state->ostate[1] = htonl(ctx.opad.B); + state->ostate[2] = htonl(ctx.opad.C); + state->ostate[3] = htonl(ctx.opad.D); + + memset(&ctx, 0, sizeof(ctx)); +} + + +heim_cram_md5 +heim_cram_md5_import(void *data, size_t len) +{ + heim_CRAM_MD5_STATE state; + heim_cram_md5 ctx; + unsigned n; + + if (len != sizeof(state)) + return NULL; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return NULL; + + memcpy(&state, data, sizeof(state)); + + ctx->ipad.A = ntohl(state.istate[0]); + ctx->ipad.B = ntohl(state.istate[1]); + ctx->ipad.C = ntohl(state.istate[2]); + ctx->ipad.D = ntohl(state.istate[3]); + + ctx->opad.A = ntohl(state.ostate[0]); + ctx->opad.B = ntohl(state.ostate[1]); + ctx->opad.C = ntohl(state.ostate[2]); + ctx->opad.D = ntohl(state.ostate[3]); + + ctx->ipad.Nl = ctx->opad.Nl = 512; + ctx->ipad.Nh = ctx->opad.Nh = 0; + ctx->ipad.num = ctx->opad.num = 0; + + return ctx; +} + +int +heim_cram_md5_verify_ctx(heim_cram_md5 ctx, const char *challenge, const char *response) +{ + uint8_t hash[CC_MD5_DIGEST_LENGTH]; + char *str = NULL; + int res; + + CC_MD5_Update(&ctx->ipad, challenge, strlen(challenge)); + CC_MD5_Final(hash, &ctx->ipad); + + CC_MD5_Update(&ctx->opad, hash, sizeof(hash)); + CC_MD5_Final(hash, &ctx->opad); + + hex_encode(hash, sizeof(hash), &str); + if (str == NULL) + return ENOMEM; + + res = (strcasecmp(str, response) != 0); + free(str); + + if (res) + return HNTLM_ERR_INVALID_CRAM_MD5; + return 0; +} + +void +heim_cram_md5_free(heim_cram_md5 ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + free(ctx); +} + + +char * +heim_cram_md5_create(const char *challenge, const char *password) +{ + CCHmacContext ctx; + uint8_t hash[CC_MD5_DIGEST_LENGTH]; + char *str = NULL; + + CCHmacInit(&ctx, kCCHmacAlgMD5, password, strlen(password)); + CCHmacUpdate(&ctx, challenge, strlen(challenge)); + CCHmacFinal(&ctx, hash); + + memset(&ctx, 0, sizeof(ctx)); + + hex_encode(hash, sizeof(hash), &str); + if (str) + strlwr(str); + + return str; +} + + int +heim_cram_md5_verify(const char *challenge, const char *password, const char *response) +{ + char *str; + int res; + + str = heim_cram_md5_create(challenge, password); + if (str == NULL) + return ENOMEM; + + res = (strcasecmp(str, response) != 0); + free(str); + + if (res) + return HNTLM_ERR_INVALID_CRAM_MD5; + return 0; +} + diff --git a/lib/ntlm/digest.c b/lib/ntlm/digest.c new file mode 100644 index 000000000..a9c5b2b05 --- /dev/null +++ b/lib/ntlm/digest.c @@ -0,0 +1,597 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "heim-auth.h" +#include "ntlm_err.h" + +struct heim_digest_desc { + int server; + int type; + char *password; + uint8_t SecretHash[CC_MD5_DIGEST_LENGTH]; + char *serverNonce; + char *serverRealm; + char *serverQOP; + char *serverMethod; + char *clientUsername; + char *clientResponse; + char *clientURI; + char *clientRealm; + char *clientNonce; + char *clientQOP; + char *clientNC; + char *serverAlgorithm; + char *auth_id; +}; + +#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 void +clear_context(heim_digest_t context) +{ + MEMSET_FREE_AND_CLEAR(context->password); + memset(context->SecretHash, 0, sizeof(context->SecretHash)); + FREE_AND_CLEAR(context->serverNonce); + FREE_AND_CLEAR(context->serverRealm); + FREE_AND_CLEAR(context->serverQOP); + FREE_AND_CLEAR(context->serverMethod); + 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); +} + +static char * +build_A1_hash(int type, + const char *username, const char *password, + const char *realm, const char *serverNonce, + const char *clientNonce, + const char *auth_id) +{ + unsigned char md[CC_MD5_DIGEST_LENGTH]; + CC_MD5_CTX ctx; + char *A1; + + CC_MD5_Init(&ctx); + CC_MD5_Update(&ctx, username, strlen(username)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, realm, strlen(realm)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, password, strlen(password)); + CC_MD5_Final(md, &ctx); + + if (type != HEIM_DIGEST_TYPE_RFC2069) { + 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, serverNonce, strlen(serverNonce)); + if (clientNonce) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, clientNonce, strlen(clientNonce)); + } + if (auth_id) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, auth_id, strlen(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, strlen(method)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientURI, strlen(context->clientURI)); + + /* conf|int */ + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + if (strcmp(context->clientQOP, "auth") != 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 ? */ + } + + 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 = *val; + 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; +} + +/* + * + */ + +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->server = server; + context->type = type; + + return context; +} + +const char * +heim_digest_generate_challenge(heim_digest_t context) +{ + return NULL; +} + +int +heim_digest_parse_challenge(heim_digest_t context, const char *challenge) +{ + struct md5_value *val = NULL; + int ret, type; + + 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; + + context->serverQOP = values_find(&val, "qop"); + if (context->serverQOP == NULL) + context->serverQOP = strdup("auth"); + if (context->serverQOP == NULL) goto out; + + /* check alg */ + + context->serverAlgorithm = values_find(&val, "algorithm"); + if (context->serverAlgorithm == NULL || strcasecmp(context->serverAlgorithm, "md5") == 0) { + type = HEIM_DIGEST_TYPE_RFC2069; + } else if (strcasecmp(context->serverAlgorithm, "md5-sess") == 0) { + type = HEIM_DIGEST_TYPE_MD5_SESS; + } else { + goto out; + } + + if (context->type != HEIM_DIGEST_TYPE_AUTO && context->type != type) + goto out; + else + context->type = type; + + + + ret = 0; + out: + free_values(val); + if (ret) + clear_context(context); + return ret; +} + +int +heim_digest_parse_response(heim_digest_t context, const char *response) +{ + struct md5_value *val = NULL; + char *nonce; + int ret; + + ret = parse_values(response, &val); + if (ret) + goto out; + + ret = 1; + + if (context->type == HEIM_DIGEST_TYPE_AUTO) + goto out; + + context->clientUsername = values_find(&val, "username"); + if (context->clientUsername == NULL) goto out; + + context->clientRealm = values_find(&val, "realm"); + + 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); + + context->clientQOP = values_find(&val, "qop"); + if (context->clientQOP == NULL) + context->clientQOP = strdup("auth"); + if (context->clientQOP == NULL) goto out; + + + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + context->clientNC = values_find(&val, "nc"); + if (context->clientNC == NULL) goto out; + + context->clientNonce = values_find(&val, "cnonce"); + if (context->clientNonce == NULL) goto out; + } + + if (context->type == HEIM_DIGEST_TYPE_RFC2069) + context->clientURI = values_find(&val, "uri"); + else + context->clientURI = values_find(&val, "digest-uri"); + if (context->clientURI == NULL) goto out; + + ret = 0; + out: + free_values(val); + return ret; +} + +const char * +heim_digest_get_key(heim_digest_t context, const char *key) +{ + if (strcmp(key, "username") == 0) { + return context->clientUsername; + } else if (strcmp(key, "realm") == 0) { + return context->clientRealm; + } else { + 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; + } else if (strcmp(key, "method") == 0) { + FREE_AND_CLEAR(context->serverMethod); + if ((context->serverMethod = strdup(value)) != NULL) + return ENOMEM; + } else { + return EINVAL; + } + return 0; +} + +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, strlen(a1)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->serverNonce, strlen(context->serverNonce)); + if (context->type != HEIM_DIGEST_TYPE_RFC2069) { + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientNC, strlen(context->clientNC)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientNonce, strlen(context->clientNonce)); + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, context->clientQOP, strlen(context->clientQOP)); + } + CC_MD5_Update(&ctx, ":", 1); + CC_MD5_Update(&ctx, a2, strlen(a2)); + CC_MD5_Final(md, &ctx); + + free(a2); + + hex_encode(md, sizeof(md), &str); + if (str) + strlwr(str); + + return str; +} + +const char * +heim_digest_create_response(heim_digest_t context) +{ + return NULL; +} + +int +heim_digest_verify(heim_digest_t context, char **response) +{ + CC_MD5_CTX ctx; + char *a1, *a2; + uint8_t md[CC_MD5_DIGEST_LENGTH]; + char *str; + int res; + + if (response) + *response = NULL; + + if (context->serverMethod == NULL) { + if (context->type != HEIM_DIGEST_TYPE_RFC2069) + context->serverMethod = strdup("AUTHENTICATE"); + else + context->serverMethod = strdup("GET"); + } + + a1 = build_A1_hash(context->type, + context->clientUsername, context->password, + context->serverRealm, context->serverNonce, + context->clientNonce, context->auth_id); + 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 */ + if (response) { + str = build_digest(context, a1, NULL); + if (str == NULL) { + MEMSET_FREE_AND_CLEAR(a1); + return ENOMEM; + } + + asprintf(response, "rspauth=%s", str); + free(str); + } + MEMSET_FREE_AND_CLEAR(a1); + + return 0; +} + +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); +} + diff --git a/lib/ntlm/heim-auth.h b/lib/ntlm/heim-auth.h new file mode 100644 index 000000000..5268d44a4 --- /dev/null +++ b/lib/ntlm/heim-auth.h @@ -0,0 +1,114 @@ +/* + * Generate challange for APOP and CRAM-MD5 + */ + +char * +heim_generate_challenge(const char *hostname); /* hostname can be NULL, the local hostname is used */ + +/* + * APOP + */ + +char * +heim_apop_create(const char *challenge, const char *password); + +int +heim_apop_verify(const char *challenge, const char *password, const char *response); + +/* + * CRAM-MD5 + */ + +typedef struct heim_HMAC_MD5_STATE_s { + uint32_t istate[4]; + uint32_t ostate[4]; +} heim_CRAM_MD5_STATE; + +typedef struct heim_cram_md5 *heim_cram_md5; + +char * +heim_cram_md5_create(const char *challenge, const char *password); + +int +heim_cram_md5_verify(const char *challenge, const char *password, const char *response); + +void +heim_cram_md5_export(const char *password, heim_CRAM_MD5_STATE *state); + +heim_cram_md5 +heim_cram_md5_import(void *data, size_t len); + +int +heim_cram_md5_verify_ctx(heim_cram_md5 ctx, const char *challenge, const char *response); + +void +heim_cram_md5_free(heim_cram_md5 ctx); + +/* + * DIGEST-MD5 + * + * heim_digest_t d; + * + * d = heim_digest_create(1, HEIM_DIGEST_TYPE_DIGEST_MD5_HTTP); + * + * if ((s = heim_digest_generate_challange(d)) != NULL) abort(); + * send_to_client(s); + * response = read_from_client(); + * + * heim_digest_parse_response(d, response); + * + * const char *user = heim_digest_get_key(d, "username"); + * heim_digest_set_key(d, "password", "sommar17"); + * + * if (heim_digest_verify(d, &response)) abort(); + * + * send_to_client(response); + * + * heim_digest_release(d); + */ + +typedef struct heim_digest_desc *heim_digest_t; + +heim_digest_t +heim_digest_create(int server, int type); + +#define HEIM_DIGEST_TYPE_AUTO 0 +#define HEIM_DIGEST_TYPE_RFC2069 1 +#define HEIM_DIGEST_TYPE_MD5 2 +#define HEIM_DIGEST_TYPE_MD5_SESS 3 + +void +heim_digest_init_set_key(heim_digest_t context, const char *key, const char *value); + +const char * +heim_digest_generate_challenge(heim_digest_t context); + +int +heim_digest_parse_challenge(heim_digest_t context, const char *challenge); + +int +heim_digest_parse_response(heim_digest_t context, const char *response); + +const char * +heim_digest_get_key(heim_digest_t context, const char *key); + +int +heim_digest_set_key(heim_digest_t context, const char *key, const char *value); + +void +heim_digest_set_user_password(heim_digest_t context, const char *password); + +void +heim_digest_set_user_h1hash(heim_digest_t context, void *ptr, size_t size); + +int +heim_digest_verify(heim_digest_t context, char **response); + +const char * +heim_digest_create_response(heim_digest_t context); + +void +heim_digest_get_session_key(heim_digest_t context, void **key, size_t *keySize); + +void +heim_digest_release(heim_digest_t context); diff --git a/lib/ntlm/test_commonauth.c b/lib/ntlm/test_commonauth.c new file mode 100644 index 000000000..6c6224f2d --- /dev/null +++ b/lib/ntlm/test_commonauth.c @@ -0,0 +1,216 @@ +/* + * 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 +#include +#include +#include +#include "heim-auth.h" + +static int +test_sasl_digest_md5(void) +{ + heim_digest_t ctx; + const char *user; + char *r; + + if ((ctx = heim_digest_create(1, HEIM_DIGEST_TYPE_AUTO)) == NULL) + abort(); + + if (heim_digest_parse_challenge(ctx, "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8")) + abort(); + if (heim_digest_parse_response(ctx, "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth")) + abort(); + + if ((user = heim_digest_get_key(ctx, "username")) == NULL) + abort(); + if (strcmp(user, "chris") != 0) + abort(); + + heim_digest_set_key(ctx, "password", "secret"); + + if (heim_digest_verify(ctx, &r)) + abort(); + + if (strcmp(r, "rspauth=ea40f60335c427b5527b84dbabcdfffd") != 0) + abort(); + + free(r); + + heim_digest_release(ctx); + + return 0; +} + +static int +test_http_digest_md5(void) +{ + heim_digest_t ctx; + const char *user; + + if ((ctx = heim_digest_create(1, HEIM_DIGEST_TYPE_AUTO)) == NULL) + abort(); + + if (heim_digest_parse_challenge(ctx, "realm=\"testrealm@host.com\"," + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + abort(); + + if (heim_digest_parse_response(ctx, "username=\"Mufasa\"," + "realm=\"testrealm@host.com\"," + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + "uri=\"/dir/index.html\"," + "response=\"1949323746fe6a43ef61f9606e7febea\"," + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")) + abort(); + + if ((user = heim_digest_get_key(ctx, "username")) == NULL) + abort(); + if (strcmp(user, "Mufasa") != 0) + abort(); + + heim_digest_set_key(ctx, "password", "CircleOfLife"); + + if (heim_digest_verify(ctx, NULL)) + abort(); + + heim_digest_release(ctx); + + return 0; +} + +static int +test_cram_md5(void) +{ + const char *chal = "<1896.697170952@postoffice.reston.mci.net>"; + const char *secret = "tanstaaftanstaaf"; + const char *resp = "b913a602c7eda7a495b4e6e7334d3890"; + heim_CRAM_MD5_STATE state; + heim_cram_md5 ctx; + char *t; + + const uint8_t *prestate = (uint8_t *) + "\x87\x1E\x24\x10\xB4\x0C\x72\x5D\xA3\x95\x2D\x5B\x8B\xFC\xDD\xE1" + "\x29\x90\xCB\xA7\x66\xF6\xB3\x40\xE8\xAC\x48\x2C\xE4\xE3\xA4\x40"; + + /* + * Test prebuild blobs + */ + + if (sizeof(state) != 32) + abort(); + + heim_cram_md5_export("foo", &state); + + if (memcmp(prestate, &state, 32) != 0) + abort(); + + /* + * Check example + */ + + + if (heim_cram_md5_verify(chal, secret, resp) != 0) + abort(); + + + /* + * Do it ourself + */ + + t = heim_cram_md5_create(chal, secret); + if (t == NULL) + abort(); + + if (strcmp(resp, t) != 0) + abort(); + + heim_cram_md5_export(secret, &state); + + /* here you can store the memcpy-ed version of state somewhere else */ + + ctx = heim_cram_md5_import(&state, sizeof(state)); + + memset(&state, 0, sizeof(state)); + + if (heim_cram_md5_verify_ctx(ctx, chal, resp) != 0) + abort(); + + heim_cram_md5_free(ctx); + + free(t); + + return 0; +} + +static int +test_apop(void) +{ + const char *chal = "<1896.697170952@dbc.mtview.ca.us>"; + const char *secret = "tanstaaf"; + const char *resp = "c4c9334bac560ecc979e58001b3e22fb"; + char *t; + + + t = heim_apop_create(chal, secret); + if (t == NULL) + abort(); + + if (strcmp(resp, t) != 0) + abort(); + + if (heim_apop_verify(chal, secret, resp) != 0) + abort(); + + free(t); + + return 0; +} + + +int +main(int argc, char **argv) +{ + int ret = 0; + + ret |= test_sasl_digest_md5(); + ret |= test_http_digest_md5(); + ret |= test_cram_md5(); + ret |= test_apop(); + + system("bash"); + + return ret; +}