summaryrefslogtreecommitdiff
path: root/usr/src/lib/libsmbfs/smb/ntlm.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libsmbfs/smb/ntlm.c')
-rw-r--r--usr/src/lib/libsmbfs/smb/ntlm.c584
1 files changed, 584 insertions, 0 deletions
diff --git a/usr/src/lib/libsmbfs/smb/ntlm.c b/usr/src/lib/libsmbfs/smb/ntlm.c
new file mode 100644
index 0000000000..8119e62b65
--- /dev/null
+++ b/usr/src/lib/libsmbfs/smb/ntlm.c
@@ -0,0 +1,584 @@
+/*
+ * 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);
+}