diff options
author | kso <kso@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2009-03-26 12:24:32 +0000 |
---|---|---|
committer | kso <kso@61a7d7f5-40b7-0310-9c16-bb0ea8cb1845> | 2009-03-26 12:24:32 +0000 |
commit | 5e49e4c5e99e185823401e004aa0a65a33a036d1 (patch) | |
tree | 40915ff4446713cf7b0bdcba17b24aab3c323195 /src/ne_ntlm.c | |
parent | 1761e1c9dde35ddbb93ca250151628280cb7b91f (diff) | |
download | neon-5e49e4c5e99e185823401e004aa0a65a33a036d1.tar.gz |
initial support for platform-independent NTLM auth
git-svn-id: http://svn.webdav.org/repos/projects/neon/trunk@1650 61a7d7f5-40b7-0310-9c16-bb0ea8cb1845
Diffstat (limited to 'src/ne_ntlm.c')
-rw-r--r-- | src/ne_ntlm.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/src/ne_ntlm.c b/src/ne_ntlm.c new file mode 100644 index 0000000..01eeb47 --- /dev/null +++ b/src/ne_ntlm.c @@ -0,0 +1,700 @@ +/* + Handling of NTLM Authentication + Copyright (C) 2003, Daniel Stenberg <daniel@haxx.se> + Copyright (C) 2009, Kai Sommerfeld <kso@openoffice.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +/* NTLM details: + + http://davenport.sourceforge.net/ntlm.html + http://www.innovation.ch/java/ntlm.html + +*/ + +#include "ne_ntlm.h" + +#ifdef HAVE_NTLM + +#include "ne_string.h" + +typedef enum { + NTLMSTATE_NONE, + NTLMSTATE_TYPE1, + NTLMSTATE_TYPE2, + NTLMSTATE_TYPE3, + NTLMSTATE_LAST +} NTLMState; + +struct ne_ntlm_context_s { + NTLMState state; + unsigned char nonce[8]; + char *user; + char *passwd; + char *requestToken; +}; + +typedef enum { + NTLM_NONE, /* not a ntlm */ + NTLM_BAD, /* an ntlm, but one we don't like */ + NTLM_FIRST, /* the first 401-reply we got with NTLM */ + NTLM_FINE, /* an ntlm we act on */ + + NTLM_LAST /* last entry in this enum, don't use */ +} ntlm; + +/* Flag bits definitions based on http://davenport.sourceforge.net/ntlm.html */ + +#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) +/* Indicates that Unicode strings are supported for use in security buffer + data. */ + +#define NTLMFLAG_NEGOTIATE_OEM (1<<1) +/* Indicates that OEM strings are supported for use in security buffer data. */ + +#define NTLMFLAG_REQUEST_TARGET (1<<2) +/* Requests that the server's authentication realm be included in the Type 2 + message. */ + +/* unknown (1<<3) */ +#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) +/* Specifies that authenticated communication between the client and server + should carry a digital signature (message integrity). */ + +#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) +/* Specifies that authenticated communication between the client and server + should be encrypted (message confidentiality). */ + +#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) +/* Indicates that the LAN Manager session key should be used for signing and + sealing authenticated communications. */ + +#define NTLMFLAG_NEGOTIATE_NETWARE (1<<8) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) +/* Indicates that NTLM authentication is being used. */ + +/* unknown (1<<10) */ +/* unknown (1<<11) */ + +#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) +/* Sent by the client in the Type 1 message to indicate that a desired + authentication realm is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) +/* Sent by the client in the Type 1 message to indicate that the client + workstation's name is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) +/* Sent by the server to indicate that the server and client are on the same + machine. Implies that the client may use a pre-established local security + context rather than responding to the challenge. */ + +#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) +/* Indicates that authenticated communication between the client and server + should be signed with a "dummy" signature. */ + +#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a domain. */ + +#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a server. */ + +#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a share. Presumably, this is for share-level + authentication. Usage is unclear. */ + +#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) +/* Indicates that the NTLM2 signing and sealing scheme should be used for + protecting authenticated communications. */ + +#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) +/* Sent by the server in the Type 2 message to indicate that it is including a + Target Information block in the message. */ + +/* unknown (1<24) */ +/* unknown (1<25) */ +/* unknown (1<26) */ +/* unknown (1<27) */ +/* unknown (1<28) */ + +#define NTLMFLAG_NEGOTIATE_128 (1<<29) +/* Indicates that 128-bit encryption is supported. */ + +#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_56 (1<<31) +/* Indicates that 56-bit encryption is supported. */ + +#ifdef HAVE_OPENSSL +/* We need OpenSSL for the crypto lib to provide us with MD4 and DES */ + +/* -- WIN32 approved -- */ +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <ctype.h> + +#include <openssl/des.h> +#include <openssl/md4.h> +#include <openssl/ssl.h> + +#if OPENSSL_VERSION_NUMBER < 0x00907001L +#define DES_key_schedule des_key_schedule +#define DES_cblock des_cblock +#define DES_set_odd_parity des_set_odd_parity +#define DES_set_key des_set_key +#define DES_ecb_encrypt des_ecb_encrypt + +/* This is how things were done in the old days */ +#define DESKEY(x) x +#define DESKEYARG(x) x +#else +/* Modern version */ +#define DESKEYARG(x) *x +#define DESKEY(x) &x +#endif + +/* Define this to make the type-3 message include the NT response message */ +#define USE_NTRESPONSES 1 + +/* + (*) = A "security buffer" is a triplet consisting of two shorts and one + long: + + 1. a 'short' containing the length of the buffer in bytes + 2. a 'short' containing the allocated space for the buffer in bytes + 3. a 'long' containing the offset to the start of the buffer from the + beginning of the NTLM message, in bytes. +*/ + +static ntlm ne_input_ntlm(ne_ntlm_context *ctx, + const char *responseToken) +{ + if(responseToken) { + /* We got a type-2 message here: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x02000000) + 12 Target Name security buffer(*) + 20 Flags long + 24 Challenge 8 bytes + (32) Context (optional) 8 bytes (two consecutive longs) + (40) Target Information (optional) security buffer(*) + 32 (48) start of data block + */ + unsigned char * buffer = NULL; + + int size = ne_unbase64(responseToken, &buffer); + + ctx->state = NTLMSTATE_TYPE2; /* we got a type-2 */ + + if(size >= 48) + /* the nonce of interest is index [24 .. 31], 8 bytes */ + memcpy(ctx->nonce, &buffer[24], 8); + + /* at index decimal 20, there's a 32bit NTLM flag field */ + + if (buffer) ne_free(buffer); + } + else { + if(ctx->state >= NTLMSTATE_TYPE1) + return NTLM_BAD; + + ctx->state = NTLMSTATE_TYPE1; /* we should sent away a type-1 */ + } + return NTLM_FINE; +} + +/* + * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * key schedule ks is also set. + */ +static void setup_des_key(unsigned char *key_56, + DES_key_schedule DESKEYARG(ks)) +{ + DES_cblock key; + + key[0] = key_56[0]; + key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); + key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); + key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); + key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); + key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); + key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); + key[7] = (key_56[6] << 1) & 0xFF; + + DES_set_odd_parity(&key); + DES_set_key(&key, ks); +} + + /* + * takes a 21 byte array and treats it as 3 56-bit DES keys. The + * 8 byte plaintext is encrypted with each key and the resulting 24 + * bytes are stored in the results array. + */ +static void calc_resp(unsigned char *keys, + unsigned char *plaintext, + unsigned char *results) +{ + DES_key_schedule ks; + + setup_des_key(keys, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys+7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+8), + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys+14, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+16), + DESKEY(ks), DES_ENCRYPT); +} + +/* + * Set up lanmanager and nt hashed passwords + */ +static void mkhash(char *password, + unsigned char *nonce, /* 8 bytes */ + unsigned char *lmresp /* must fit 0x18 bytes */ +#ifdef USE_NTRESPONSES + , unsigned char *ntresp /* must fit 0x18 bytes */ +#endif + ) +{ + unsigned char lmbuffer[21]; +#ifdef USE_NTRESPONSES + unsigned char ntbuffer[21]; +#endif + unsigned char *pw; + static const unsigned char magic[] = { + 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 + }; + int i; + int len = strlen(password); + + /* make it fit at least 14 bytes */ + pw = malloc(len<7?14:len*2); + if(!pw) + return; /* this will lead to a badly generated package */ + + if (len > 14) + len = 14; + + for (i=0; i<len; i++) + pw[i] = toupper(password[i]); + + for (; i<14; i++) + pw[i] = 0; + + { + /* create LanManager hashed password */ + DES_key_schedule ks; + + setup_des_key(pw, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)lmbuffer, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(pw+7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)(lmbuffer+8), + DESKEY(ks), DES_ENCRYPT); + + memset(lmbuffer+16, 0, 5); + } + /* create LM responses */ + calc_resp(lmbuffer, nonce, lmresp); + +#ifdef USE_NTRESPONSES + { + /* create NT hashed password */ + MD4_CTX MD4; + + len = strlen(password); + + for (i=0; i<len; i++) { + pw[2*i] = password[i]; + pw[2*i+1] = 0; + } + + MD4_Init(&MD4); + MD4_Update(&MD4, pw, 2*len); + MD4_Final(ntbuffer, &MD4); + + memset(ntbuffer+16, 0, 8); + } + + calc_resp(ntbuffer, nonce, ntresp); +#endif + + free(pw); +} + +#define SHORTPAIR(x) ((x) & 0xff), ((x) >> 8) +#define LONGQUARTET(x) ((x) & 0xff), (((x) >> 8)&0xff), \ + (((x) >>16)&0xff), ((x)>>24) + +/* this is for creating ntlm header output */ +static int ne_output_ntlm(ne_ntlm_context *ctx) +{ + const char *domain=""; /* empty */ + const char *host=""; /* empty */ + int domlen=strlen(domain); + int hostlen = strlen(host); + int hostoff; /* host name offset */ + int domoff; /* domain name offset */ + int size; + unsigned char ntlmbuf[256]; /* enough, unless the host/domain is very long */ + + if(!ctx->user || !ctx->passwd) + /* no user, no auth */ + return 0; /* OK */ + + switch(ctx->state) { + case NTLMSTATE_TYPE1: + default: /* for the weird cases we (re)start here */ + hostoff = 32; + domoff = hostoff + hostlen; + + /* Create and send a type-1 message: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x01000000) + 12 Flags long + 16 Supplied Domain security buffer(*) + 24 Supplied Workstation security buffer(*) + 32 start of data block + + */ + + snprintf((char *)ntlmbuf, sizeof(ntlmbuf), "NTLMSSP%c" + "\x01%c%c%c" /* 32-bit type = 1 */ + "%c%c%c%c" /* 32-bit NTLM flag field */ + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host name offset */ + "%c%c" /* 2 zeroes */ + "%s" /* host name */ + "%s", /* domain string */ + 0, /* trailing zero */ + 0,0,0, /* part of type-1 long */ + + LONGQUARTET( + NTLMFLAG_NEGOTIATE_OEM| /* 2 */ + NTLMFLAG_NEGOTIATE_NTLM_KEY /* 200 */ + /* equals 0x0202 */ + ), + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0,0, + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0,0, + host, domain); + + /* initial packet length */ + size = 32 + hostlen + domlen; + + /* now keeper of the base64 encoded package size */ + if (ctx->requestToken) ne_free(ctx->requestToken); + ctx->requestToken = ne_base64(ntlmbuf, size); + + break; + + case NTLMSTATE_TYPE2: + /* We received the type-2 already, create a type-3 message: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x03000000) + 12 LM/LMv2 Response security buffer(*) + 20 NTLM/NTLMv2 Response security buffer(*) + 28 Domain Name security buffer(*) + 36 User Name security buffer(*) + 44 Workstation Name security buffer(*) + (52) Session Key (optional) security buffer(*) + (60) Flags (optional) long + 52 (64) start of data block + + */ + + { + int lmrespoff; + int ntrespoff; + int useroff; + unsigned char lmresp[0x18]; /* fixed-size */ +#ifdef USE_NTRESPONSES + unsigned char ntresp[0x18]; /* fixed-size */ +#endif + const char *user; + int userlen; + + user = strchr(ctx->user, '\\'); + if(!user) + user = strchr(ctx->user, '/'); + + if (user) { + domain = ctx->user; + domlen = user - domain; + user++; + } + else + user = ctx->user; + userlen = strlen(user); + + mkhash(ctx->passwd, &ctx->nonce[0], lmresp +#ifdef USE_NTRESPONSES + , ntresp +#endif + ); + + domoff = 64; /* always */ + useroff = domoff + domlen; + hostoff = useroff + userlen; + lmrespoff = hostoff + hostlen; + ntrespoff = lmrespoff + 0x18; + + /* Create the big type-3 message binary blob */ + size = snprintf((char *)ntlmbuf, sizeof(ntlmbuf), + "NTLMSSP%c" + "\x03%c%c%c" /* type-3, 32 bits */ + + "%c%c%c%c" /* LanManager length + allocated space */ + "%c%c" /* LanManager offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* NT-response length */ + "%c%c" /* NT-response allocated space */ + "%c%c" /* NT-response offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* user length */ + "%c%c" /* user allocated space */ + "%c%c" /* user offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host offset */ + "%c%c%c%c%c%c" /* 6 zeroes */ + + "\xff\xff" /* message length */ + "%c%c" /* 2 zeroes */ + + "\x01\x82" /* flags */ + "%c%c" /* 2 zeroes */ + + /* domain string */ + /* user string */ + /* host string */ + /* LanManager response */ + /* NT response */ + , + 0, /* zero termination */ + 0,0,0, /* type-3 long, the 24 upper bits */ + + SHORTPAIR(0x18), /* LanManager response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(lmrespoff), + 0x0, 0x0, + +#ifdef USE_NTRESPONSES + SHORTPAIR(0x18), /* NT-response length, twice */ + SHORTPAIR(0x18), +#else + 0x0, 0x0, + 0x0, 0x0, +#endif + SHORTPAIR(ntrespoff), + 0x0, 0x0, + + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0x0, 0x0, + + SHORTPAIR(userlen), + SHORTPAIR(userlen), + SHORTPAIR(useroff), + 0x0, 0x0, + + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + 0x0, 0x0, + + 0x0, 0x0); + + /* size is now 64 */ + size=64; + ntlmbuf[62]=ntlmbuf[63]=0; + + /* Make sure that the user and domain strings fit in the target buffer + before we copy them there. */ + if(size + userlen + domlen >= sizeof(ntlmbuf)) { + return -1; + } + + memcpy(&ntlmbuf[size], domain, domlen); + size += domlen; + + memcpy(&ntlmbuf[size], user, userlen); + size += userlen; + + /* we append the binary hashes to the end of the blob */ + if(size < ((int)sizeof(ntlmbuf) - 0x18)) { + memcpy(&ntlmbuf[size], lmresp, 0x18); + size += 0x18; + } + +#ifdef USE_NTRESPONSES + if(size < ((int)sizeof(ntlmbuf) - 0x18)) { + memcpy(&ntlmbuf[size], ntresp, 0x18); + size += 0x18; + } +#endif + + ntlmbuf[56] = size & 0xff; + ntlmbuf[57] = size >> 8; + + /* convert the binary blob into base64 */ + ctx->requestToken = ne_base64(ntlmbuf, size); + + ctx->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ + } + break; + + case NTLMSTATE_TYPE3: + /* connection is already authenticated, + * don't send a header in future requests */ + if (ctx->requestToken) ne_free(ctx->requestToken); + ctx->requestToken = NULL; + break; + } + + return 0; /* OK */ +} + +int ne_ntlm_create_context(ne_ntlm_context **context, const char *userName, const char *password) +{ + if (context == NULL) { + return -1; + } else { + ne_ntlm_context *ctx = ne_calloc(sizeof(ne_ntlm_context)); + + ctx->state = NTLMSTATE_NONE; + ctx->user = ne_strdup(userName); + ctx->passwd = ne_strdup(password); + + *context = ctx; + return 0; + } +} + +int ne_ntlm_destroy_context(ne_ntlm_context *context) +{ + if (context != NULL) { + if (context->user) + ne_free(context->user); + + if (context->passwd) + ne_free(context->passwd); + + if (context->requestToken) + ne_free(context->requestToken); + + ne_free(context); + } + return 0; +} + +int ne_ntlm_clear_context(ne_ntlm_context *context) +{ + return 0; +} + +int ne_ntlm_authenticate(ne_ntlm_context *context, const char *responseToken) +{ + if (context == NULL) { + return -1; + } else { + if (context->state <= NTLMSTATE_TYPE3) { + ntlm ntlmstatus = ne_input_ntlm(context, responseToken); + + if (ntlmstatus != NTLM_FINE) { + return -1; + } + } + } + return ne_output_ntlm(context); +} + +char *ne_ntlm_getRequestToken(ne_ntlm_context *context) +{ + if (context == NULL) { + return NULL; + } else { + if (context->requestToken) { + char *ret = ne_strdup(context->requestToken); + ne_free(context->requestToken); + context->requestToken = NULL; + return ret; + } else { + return NULL; + } + } +} + +#endif /* HAVE_OPENSSL */ +#endif /* HAVE_NTLM */ |