text: /*
   * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
   * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
   * 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
   *
   * Given a password, compute the LM hash.
   * a.k.a. ResponseKeyLM in [MS-NLMP]
   *
   * Output:
   *	hash: 16-byte "LanMan" (LM) hash (normally ctx->ct_lmhash)
   * 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
   *
   * Given a password, compute the NT hash.
   * a.k.a. the ResponseKeyNT in [MS-NLMP]
   *
   * Output:
   *	hash: 16-byte "NT" hash (normally ctx->ct_nthash)
   * 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
   * a.k.a. DESL() in [MS-NLMP]
   *
   * 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 *ssn_key)
  {
  	uchar_t *lmresp, *ntresp;
  	int err;
  
  	/* Get mbuf chain for the LM response. */
  	if ((err = mb_init_sz(lm_mbp, NTLM_V1_RESP_SZ)) != 0)
  		return (err);
  
  	/* Get mbuf chain for the NT response. */
  	if ((err = mb_init_sz(nt_mbp, NTLM_V1_RESP_SZ)) != 0)
  		return (err);
  
  	/*
  	 * Compute the NTLM response, derived from
  	 * the challenge and the NT hash (a.k.a ResponseKeyNT)
  	 */
  	err = mb_fit(nt_mbp, NTLM_V1_RESP_SZ, (char **)&ntresp);
  	if (err)
  		return (err);
  	bzero(ntresp, NTLM_V1_RESP_SZ);
  	err = ntlm_v1_response(ntresp, ctx->ct_nthash,
  	    ctx->ct_srv_chal, NTLM_CHAL_SZ);
  
  	/*
  	 * Compute the LM response, derived from
  	 * the challenge and the ASCII password.
  	 * Per. [MS-NLMP 3.3.1] if NoLmResponse,
  	 * send the NT response for both NT+LM.
  	 */
  	err = mb_fit(lm_mbp, NTLM_V1_RESP_SZ, (char **)&lmresp);
  	if (err)
  		return (err);
  	memcpy(lmresp, ntresp, 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_srv_chal, NTLM_CHAL_SZ);
  		if (err)
  			return (err);
  	}
  
  	/*
  	 * Compute the session key
  	 */
  	ntlm_v1_session_key(ssn_key, ctx->ct_nthash);
  
  	return (err);
  }
  
  /*
   * Compute both the LM(v1x) response and the NTLM(v1x) response,
   * and put them in the mbdata chains passed.  "v1x" here refers to
   * NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY used with NTLMSSP,
   * also known by its shorter alias NTLMSSP_NEGOTIATE_NTLM2.
   * [MS-NLMP 3.3.1]
   *
   * This allocates mbuf chains in the output args (caller frees).
   */
  int
  ntlm_put_v1x_responses(struct smb_ctx *ctx,
  	struct mbdata *lm_mbp, struct mbdata *nt_mbp,
  	uchar_t *ssn_key)
  {
  	MD5_CTX context;
  	uchar_t challenges[2 * NTLM_CHAL_SZ];
  	uchar_t digest[NTLM_HASH_SZ];
  	uchar_t *lmresp, *ntresp;
  	int err;
  
  	/* Get mbuf chain for the LM response. */
  	if ((err = mb_init_sz(lm_mbp, NTLM_V1_RESP_SZ)) != 0)
  		return (err);
  
  	/* Get mbuf chain for the NT response. */
  	if ((err = mb_init_sz(nt_mbp, NTLM_V1_RESP_SZ)) != 0)
  		return (err);
  
  	/*
  	 * challenges = ConcatenationOf(ServerChallenge, ClientChallenge)
  	 */
  	memcpy(challenges, ctx->ct_srv_chal, NTLM_CHAL_SZ);
  	memcpy(challenges + NTLM_CHAL_SZ, ctx->ct_clnonce, NTLM_CHAL_SZ);
  
  	/*
  	 * digest = MD5(challenges)
  	 */
  	MD5Init(&context);
  	MD5Update(&context, challenges, sizeof (challenges));
  	MD5Final(digest, &context);
  
  	/*
  	 * Compute the NTLM response, derived from the
  	 * NT hash (a.k.a ResponseKeyNT) and the first
  	 * 8 bytes of the MD5 digest of the challenges.
  	 */
  	err = mb_fit(nt_mbp, NTLM_V1_RESP_SZ, (char **)&ntresp);
  	if (err)
  		return (err);
  	bzero(ntresp, NTLM_V1_RESP_SZ);
  	err = ntlm_v1_response(ntresp, ctx->ct_nthash,
  	    digest, NTLM_CHAL_SZ);
  
  	/*
  	 * With "Extended Session Security", the LM response
  	 * is simply the client challenge (nonce) padded out.
  	 */
  	err = mb_fit(lm_mbp, NTLM_V1_RESP_SZ, (char **)&lmresp);
  	if (err)
  		return (err);
  	bzero(lmresp, NTLM_V1_RESP_SZ);
  	memcpy(lmresp, ctx->ct_clnonce, NTLM_CHAL_SZ);
  
  	/*
  	 * Compute the session key
  	 */
  	ntlm_v1_session_key(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 *ssn_key)
  {
  	uchar_t *lmresp, *ntresp;
  	int err;
  	char *ucuser = NULL;	/* upper-case user name */
  	uchar_t v2hash[NTLM_HASH_SZ];
  	struct mbuf *tim = ti_mbp->mb_top;
  
  	/*
  	 * Convert the user name to upper-case, as
  	 * that's what's used when computing LMv2
  	 * and NTLMv2 responses.  Note that the
  	 * domain name is NOT upper-cased!
  	 */
  	if (ctx->ct_user[0] == '\0')
  		return (EINVAL);
  	ucuser = utf8_str_toupper(ctx->ct_user);
  	if (ucuser == NULL)
  		return (ENOMEM);
  
  	if ((err = mb_init(lm_mbp)) != 0)
  		goto out;
  	if ((err = mb_init(nt_mbp)) != 0)
  		goto out;
  
  	/*
  	 * Compute the NTLMv2 hash
  	 */
  	err = ntlm_v2_hash(v2hash, ctx->ct_nthash,
  	    ucuser, ctx->ct_domain);
  	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 = mb_reserve(lm_mbp, NTLM_HASH_SZ);
  	err = ntlm_v2_resp_hash(lmresp,
  	    v2hash, ctx->ct_srv_chal,
  	    ctx->ct_clnonce, NTLM_CHAL_SZ);
  	if (err)
  		goto out;
  	mb_put_mem(lm_mbp, ctx->ct_clnonce, NTLM_CHAL_SZ, MB_MSYSTEM);
  
  	/*
  	 * 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 = mb_reserve(nt_mbp, NTLM_HASH_SZ);
  	err = ntlm_v2_resp_hash(ntresp,
  	    v2hash, ctx->ct_srv_chal,
  	    (uchar_t *)tim->m_data, tim->m_len);
  	if (err)
  		goto out;
  	mb_put_mem(nt_mbp, tim->m_data, tim->m_len, MB_MSYSTEM);
  
  	/*
  	 * Compute the session key
  	 */
  	ntlm_v2_session_key(ssn_key, v2hash, ntresp);
  
  out:
  	if (err) {
  		mb_done(lm_mbp);
  		mb_done(nt_mbp);
  	}
  	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, MB_MSYSTEM);
  
  	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)) != 0)
  		return (err);
  
  	/*
  	 * 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_MSYSTEM);
  	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, MB_MSYSTEM);
  	} 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);
  }
  
  /*
   * Helper for ntlmssp_put_type3 - Build the "key exchange key"
   * used when we have both NTLM(v1) and NTLMSSP_NEGOTIATE_NTLM2.
   * HMAC_MD5(SessionBaseKey, concat(ServerChallenge, LmResponse[0..7]))
   */
  void
  ntlm2_kxkey(struct smb_ctx *ctx, struct mbdata *lm_mbp,
  	uchar_t *ssn_key, uchar_t *kxkey)
  {
  	uchar_t data[NTLM_HASH_SZ];
  	uchar_t *p = mtod(lm_mbp->mb_top, uchar_t *);
  
  	/* concat(ServerChallenge, LmResponse[0..7]) */
  	memcpy(data, ctx->ct_srv_chal, NTLM_CHAL_SZ);
  	memcpy(data + NTLM_CHAL_SZ, p, NTLM_CHAL_SZ);
  
  	/* HMAC_MD5(SessionBaseKey, concat(...)) */
  	HMACT64(kxkey, ssn_key, NTLM_HASH_SZ,
  	    data, NTLM_HASH_SZ);
  }
  