view usr/src/lib/libsmbfs/smb/ntlm.c @ 10023:71bf38dba3d6

6584198 SMB Client needs authentication improvements 6587713 Need to reconnect after server disconnect
author Gordon Ross <Gordon.Ross@Sun.COM>
date Thu, 02 Jul 2009 12:58:38 -0400
parents
children ed3411181494
line wrap: on
line source

/*
 * 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);
}