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