ntlm.c revision 613a2f6ba31e891e3d947a356daf5e563d43c1ce
/*
* Copyright (c) 2000-2001, Boris Popov
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* $Id: smb_crypt.c,v 1.13 2005/01/26 23:50:50 lindak Exp $
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* NTLM support functions
*
* Some code from the driver: smb_smb.c, smb_crypt.c
*/
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/md4.h>
#include <sys/md5.h>
#include <ctype.h>
#include <stdlib.h>
#include <strings.h>
#include <netsmb/smb_lib.h>
#include "private.h"
#include "charsets.h"
#include "smb_crypt.h"
#include "ntlm.h"
/*
* ntlm_compute_lm_hash
*
* Compute an LM hash given a password
*
* Output:
* hash: 16-byte "LanMan" (LM) hash.
* Inputs:
* ucpw: User's password, upper-case UTF-8 string.
*
* Source: Implementing CIFS (Chris Hertel)
*
* P14 = UCPW padded to 14-bytes, or truncated (as needed)
* result = Encrypt(Key=P14, Data=MagicString)
*/
int
ntlm_compute_lm_hash(uchar_t *hash, const char *pass)
{
static const uchar_t M8[8] = "KGS!@#$%";
uchar_t P14[14 + 1];
int err;
char *ucpw;
/* First, convert the p/w to upper case. */
ucpw = utf8_str_toupper(pass);
if (ucpw == NULL)
return (ENOMEM);
/* Pad or truncate the upper-case P/W as needed. */
bzero(P14, sizeof (P14));
(void) strncpy((char *)P14, ucpw, 14);
/* Compute the hash. */
err = smb_encrypt_DES(hash, NTLM_HASH_SZ,
P14, 14, M8, 8);
free(ucpw);
return (err);
}
/*
* ntlm_compute_nt_hash
*
* Compute an NT hash given a password in UTF-8.
*
* Output:
* hash: 16-byte "NT" hash.
* Inputs:
* upw: User's password, mixed-case UCS-2LE.
* pwlen: Size (in bytes) of upw
*/
int
ntlm_compute_nt_hash(uchar_t *hash, const char *pass)
{
MD4_CTX ctx;
uint16_t *unipw = NULL;
int pwsz;
/* First, convert the password to unicode. */
unipw = convert_utf8_to_leunicode(pass);
if (unipw == NULL)
return (ENOMEM);
pwsz = unicode_strlen(unipw) << 1;
/* Compute the hash. */
MD4Init(&ctx);
MD4Update(&ctx, unipw, pwsz);
MD4Final(hash, &ctx);
free(unipw);
return (0);
}
/*
* ntlm_v1_response
*
* Create an LM response from the given LM hash and challenge,
* or an NTLM repsonse from a given NTLM hash and challenge.
* Both response types are 24 bytes (NTLM_V1_RESP_SZ)
*/
static int
ntlm_v1_response(uchar_t *resp,
const uchar_t *hash,
const uchar_t *chal, int clen)
{
uchar_t S21[21];
int err;
/*
* 14-byte LM Hash should be padded with 5 nul bytes to create
* a 21-byte string to be used in producing LM response
*/
bzero(&S21, sizeof (S21));
bcopy(hash, S21, NTLM_HASH_SZ);
/* padded LM Hash -> LM Response */
err = smb_encrypt_DES(resp, NTLM_V1_RESP_SZ,
S21, 21, chal, clen);
return (err);
}
/*
* Calculate an NTLMv1 session key (16 bytes).
*/
static void
ntlm_v1_session_key(uchar_t *ssn_key, const uchar_t *nt_hash)
{
MD4_CTX md4;
MD4Init(&md4);
MD4Update(&md4, nt_hash, NTLM_HASH_SZ);
MD4Final(ssn_key, &md4);
}
/*
* Compute both the LM(v1) response and the NTLM(v1) response,
* and put them in the mbdata chains passed. This allocates
* mbuf chains in the output args, which the caller frees.
*/
int
ntlm_put_v1_responses(struct smb_ctx *ctx,
struct mbdata *lm_mbp, struct mbdata *nt_mbp)
{
uchar_t *lmresp, *ntresp;
int err;
/* Get mbuf chain for the LM response. */
if ((err = mb_init(lm_mbp, NTLM_V1_RESP_SZ)) != 0)
return (err);
/* Get mbuf chain for the NT response. */
if ((err = mb_init(nt_mbp, NTLM_V1_RESP_SZ)) != 0)
return (err);
/*
* Compute the LM response, derived
* from the challenge and the ASCII
* password (if authflags allow).
*/
mb_fit(lm_mbp, NTLM_V1_RESP_SZ, (char **)&lmresp);
bzero(lmresp, NTLM_V1_RESP_SZ);
if (ctx->ct_authflags & SMB_AT_LM1) {
/* They asked to send the LM hash too. */
err = ntlm_v1_response(lmresp, ctx->ct_lmhash,
ctx->ct_ntlm_chal, NTLM_CHAL_SZ);
if (err)
return (err);
}
/*
* Compute the NTLM response, derived from
* the challenge and the NT hash.
*/
mb_fit(nt_mbp, NTLM_V1_RESP_SZ, (char **)&ntresp);
bzero(ntresp, NTLM_V1_RESP_SZ);
err = ntlm_v1_response(ntresp, ctx->ct_nthash,
ctx->ct_ntlm_chal, NTLM_CHAL_SZ);
/*
* Compute the session key
*/
ntlm_v1_session_key(ctx->ct_ssn_key, ctx->ct_nthash);
return (err);
}
/*
* A variation on HMAC-MD5 known as HMACT64 is used by Windows systems.
* The HMACT64() function is the same as the HMAC-MD5() except that
* it truncates the input key to 64 bytes rather than hashing it down
* to 16 bytes using the MD5() function.
*
* Output: digest (16-bytes)
*/
static void
HMACT64(uchar_t *digest,
const uchar_t *key, size_t key_len,
const uchar_t *data, size_t data_len)
{
MD5_CTX context;
uchar_t k_ipad[64]; /* inner padding - key XORd with ipad */
uchar_t k_opad[64]; /* outer padding - key XORd with opad */
int i;
/* if key is longer than 64 bytes use only the first 64 bytes */
if (key_len > 64)
key_len = 64;
/*
* The HMAC-MD5 (and HMACT64) transform looks like:
*
* MD5(K XOR opad, MD5(K XOR ipad, data))
*
* where K is an n byte key
* ipad is the byte 0x36 repeated 64 times
* opad is the byte 0x5c repeated 64 times
* and data is the data being protected.
*/
/* start out by storing key in pads */
bzero(k_ipad, sizeof (k_ipad));
bzero(k_opad, sizeof (k_opad));
bcopy(key, k_ipad, key_len);
bcopy(key, k_opad, key_len);
/* XOR key with ipad and opad values */
for (i = 0; i < 64; i++) {
k_ipad[i] ^= 0x36;
k_opad[i] ^= 0x5c;
}
/*
* perform inner MD5
*/
MD5Init(&context); /* init context for 1st pass */
MD5Update(&context, k_ipad, 64); /* start with inner pad */
MD5Update(&context, data, data_len); /* then data of datagram */
MD5Final(digest, &context); /* finish up 1st pass */
/*
* perform outer MD5
*/
MD5Init(&context); /* init context for 2nd pass */
MD5Update(&context, k_opad, 64); /* start with outer pad */
MD5Update(&context, digest, 16); /* then results of 1st hash */
MD5Final(digest, &context); /* finish up 2nd pass */
}
/*
* Compute an NTLMv2 hash given the NTLMv1 hash, the user name,
* and the destination (machine or domain name).
*
* Output:
* v2hash: 16-byte NTLMv2 hash.
* Inputs:
* v1hash: 16-byte NTLMv1 hash.
* user: User name, UPPER-case UTF-8 string.
* destination: Domain or server, MIXED-case UTF-8 string.
*/
static int
ntlm_v2_hash(uchar_t *v2hash, const uchar_t *v1hash,
const char *user, const char *destination)
{
int ulen, dlen;
size_t ucs2len;
uint16_t *ucs2data = NULL;
char *utf8data = NULL;
int err = ENOMEM;
/*
* v2hash = HMACT64(v1hash, 16, concat(upcase(user), dest))
* where "dest" is the domain or server name ("target name")
* Note: user name is converted to upper-case by the caller.
*/
/* utf8data = concat(user, dest) */
ulen = strlen(user);
dlen = strlen(destination);
utf8data = malloc(ulen + dlen + 1);
if (utf8data == NULL)
goto out;
bcopy(user, utf8data, ulen);
bcopy(destination, utf8data + ulen, dlen + 1);
/* Convert to UCS-2LE */
ucs2data = convert_utf8_to_leunicode(utf8data);
if (ucs2data == NULL)
goto out;
ucs2len = 2 * unicode_strlen(ucs2data);
HMACT64(v2hash, v1hash, NTLM_HASH_SZ,
(uchar_t *)ucs2data, ucs2len);
err = 0;
out:
if (ucs2data)
free(ucs2data);
if (utf8data)
free(utf8data);
return (err);
}
/*
* Compute a partial LMv2 or NTLMv2 response (first 16-bytes).
* The full response is composed by the caller by
* appending the client_data to the returned hash.
*
* Output:
* rhash: _partial_ LMv2/NTLMv2 response (first 16-bytes)
* Inputs:
* v2hash: 16-byte NTLMv2 hash.
* C8: Challenge from server (8 bytes)
* client_data: client nonce (for LMv2) or the
* "blob" from ntlm_build_target_info (NTLMv2)
*/
static int
ntlm_v2_resp_hash(uchar_t *rhash,
const uchar_t *v2hash, const uchar_t *C8,
const uchar_t *client_data, size_t cdlen)
{
size_t dlen;
uchar_t *data = NULL;
/* data = concat(C8, client_data) */
dlen = 8 + cdlen;
data = malloc(dlen);
if (data == NULL)
return (ENOMEM);
bcopy(C8, data, 8);
bcopy(client_data, data + 8, cdlen);
HMACT64(rhash, v2hash, NTLM_HASH_SZ, data, dlen);
free(data);
return (0);
}
/*
* Calculate an NTLMv2 session key (16 bytes).
*/
static void
ntlm_v2_session_key(uchar_t *ssn_key,
const uchar_t *v2hash,
const uchar_t *ntresp)
{
/* session key uses only 1st 16 bytes of ntresp */
HMACT64(ssn_key, v2hash, NTLM_HASH_SZ, ntresp, NTLM_HASH_SZ);
}
/*
* Compute both the LMv2 response and the NTLMv2 response,
* and put them in the mbdata chains passed. This allocates
* mbuf chains in the output args, which the caller frees.
* Also computes the session key.
*/
int
ntlm_put_v2_responses(struct smb_ctx *ctx, struct mbdata *ti_mbp,
struct mbdata *lm_mbp, struct mbdata *nt_mbp)
{
uchar_t *lmresp, *ntresp;
int err;
char *ucdom = NULL; /* user's domain */
char *ucuser = NULL; /* account name */
uchar_t v2hash[NTLM_HASH_SZ];
struct mbuf *tim = ti_mbp->mb_top;
if ((err = mb_init(lm_mbp, M_MINSIZE)) != 0)
return (err);
if ((err = mb_init(nt_mbp, M_MINSIZE)) != 0)
return (err);
/*
* Convert the user name to upper-case, as
* that's what's used when computing LMv2
* and NTLMv2 responses. Also the domain.
*/
ucdom = utf8_str_toupper(ctx->ct_domain);
ucuser = utf8_str_toupper(ctx->ct_user);
if (ucdom == NULL || ucuser == NULL) {
err = ENOMEM;
goto out;
}
/*
* Compute the NTLMv2 hash (see above)
* Needs upper-case user, domain.
*/
err = ntlm_v2_hash(v2hash, ctx->ct_nthash, ucuser, ucdom);
if (err)
goto out;
/*
* Compute the LMv2 response, derived from
* the v2hash, the server challenge, and
* the client nonce (random bits).
*
* We compose it from two parts:
* 1: 16-byte response hash
* 2: Client nonce
*/
lmresp = (uchar_t *)lm_mbp->mb_pos;
mb_put_mem(lm_mbp, NULL, NTLM_HASH_SZ);
err = ntlm_v2_resp_hash(lmresp,
v2hash, ctx->ct_ntlm_chal,
ctx->ct_clnonce, NTLM_CHAL_SZ);
if (err)
goto out;
mb_put_mem(lm_mbp, ctx->ct_clnonce, NTLM_CHAL_SZ);
/*
* Compute the NTLMv2 response, derived
* from the server challenge and the
* "target info." blob passed in.
*
* Again composed from two parts:
* 1: 16-byte response hash
* 2: "target info." blob
*/
ntresp = (uchar_t *)nt_mbp->mb_pos;
mb_put_mem(nt_mbp, NULL, NTLM_HASH_SZ);
err = ntlm_v2_resp_hash(ntresp,
v2hash, ctx->ct_ntlm_chal,
(uchar_t *)tim->m_data, tim->m_len);
if (err)
goto out;
mb_put_mem(nt_mbp, tim->m_data, tim->m_len);
/*
* Compute the session key
*/
ntlm_v2_session_key(ctx->ct_ssn_key, v2hash, ntresp);
out:
if (err) {
mb_done(lm_mbp);
mb_done(nt_mbp);
}
free(ucdom);
free(ucuser);
return (err);
}
/*
* Helper for ntlm_build_target_info below.
* Put a name in the NTLMv2 "target info." blob.
*/
static void
smb_put_blob_name(struct mbdata *mbp, char *name, int type)
{
uint16_t *ucs = NULL;
int nlen;
if (name)
ucs = convert_utf8_to_leunicode(name);
if (ucs)
nlen = unicode_strlen(ucs);
else
nlen = 0;
nlen <<= 1; /* length in bytes, without null. */
mb_put_uint16le(mbp, type);
mb_put_uint16le(mbp, nlen);
mb_put_mem(mbp, (char *)ucs, nlen);
if (ucs)
free(ucs);
}
/*
* Build an NTLMv2 "target info." blob. When called from NTLMSSP,
* the list of names comes from the Type 2 message. Otherwise,
* we create the name list here.
*/
int
ntlm_build_target_info(struct smb_ctx *ctx, struct mbuf *names,
struct mbdata *mbp)
{
struct timeval now;
uint64_t nt_time;
char *ucdom = NULL; /* user's domain */
int err;
/* Get mbuf chain for the "target info". */
if ((err = mb_init(mbp, M_MINSIZE)) != 0)
return (err);
/*
* Construct the client nonce by getting
* some random data from /dev/urandom
*/
err = smb_get_urandom(ctx->ct_clnonce, NTLM_CHAL_SZ);
if (err)
goto out;
/*
* Get the "NT time" for the target info header.
*/
(void) gettimeofday(&now, 0);
smb_time_local2NT(&now, 0, &nt_time);
/*
* Build the "target info." block.
*
* Based on information at:
* http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
*
* First the fixed-size part.
*/
mb_put_uint32le(mbp, 0x101); /* Blob signature */
mb_put_uint32le(mbp, 0); /* reserved */
mb_put_uint64le(mbp, nt_time); /* NT time stamp */
mb_put_mem(mbp, ctx->ct_clnonce, NTLM_CHAL_SZ);
mb_put_uint32le(mbp, 0); /* unknown */
/*
* Now put the list of names, either from the
* NTLMSSP Type 2 message or composed here.
*/
if (names) {
err = mb_put_mem(mbp, names->m_data, names->m_len);
} else {
/* Get upper-case names. */
ucdom = utf8_str_toupper(ctx->ct_domain);
if (ucdom == NULL) {
err = ENOMEM;
goto out;
}
smb_put_blob_name(mbp, ucdom, NAMETYPE_DOMAIN_NB);
smb_put_blob_name(mbp, NULL, NAMETYPE_EOL);
/* OK, that's the whole "target info." blob! */
}
err = 0;
out:
free(ucdom);
return (err);
}