/* Handling of NTLM Authentication Copyright (C) 2003, Daniel Stenberg Copyright (C) 2009, Kai Sommerfeld 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 #include #include #include #include #include #include #include #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 = ne_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> 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 */ ne_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 */ ne_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_t)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 */ } ne_ntlm_context *ne__ntlm_create_context(const char *userName, const char *password) { ne_ntlm_context *ctx = ne_calloc(sizeof(ne_ntlm_context)); ctx->state = NTLMSTATE_NONE; ctx->user = ne_strdup(userName); ctx->passwd = ne_strdup(password); return ctx; } void ne__ntlm_destroy_context(ne_ntlm_context *context) { if (context->user) ne_free(context->user); if (context->passwd) ne_free(context->passwd); if (context->requestToken) ne_free(context->requestToken); ne_free(context); } int ne__ntlm_authenticate(ne_ntlm_context *context, const char *responseToken) { if (context == NULL) { return -1; } else { if (!responseToken && (context->state == NTLMSTATE_TYPE3)) context->state = NTLMSTATE_NONE; 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) { char *ret; if (context == NULL || !context->requestToken) { return NULL; } ret = ne_strdup(context->requestToken); ne_free(context->requestToken); context->requestToken = NULL; return ret; } #endif /* HAVE_OPENSSL */ #endif /* HAVE_NTLM */