NtlmGenerator.c   [plain text]


/*
 * Copyright (c) 2000-2004,2006-2007,2011,2013 Apple Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * NtlmGenerator.c - NTLM client-side authentication engine. 
 *
 * In the usual absence of documentation from Microsoft, the "inventors" of this
 * protocol, this module was written using the superb revers engineering documented
 * at 
 *
 *     http://davenport.sourceforge.net/ntlm.html#localAuthentication
 */

#include "NtlmGenerator.h"
#include "ntlmBlobPriv.h"
#include <Security/SecBase.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <strings.h>

/* 
 * For debugging using fixed server challenge and client nonce. 
 */
#if		DEBUG_FIXED_CHALLENGE

/* these are "test vectors", effectively, from sourceforge */
/* use pwd SecREt01, host/domain DOMAIN */
static const unsigned char fixServerChallenge[8] = 
	{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
static const unsigned char fixClientNonce[8] = 
	{ 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44 };

static const unsigned char fixTargetInfo[] = {
	0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00,
	0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00,
	0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00,
	0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00,
	0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00,
	0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
	0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00,
	0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 
	0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00,
	0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00,
	0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00,
	0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00,
	0x00, 0x00
};
#endif

/* app's NtlmGeneratorRef is a pointer to one of these */
struct NtlmGenerator {
	NLTM_Which			mWhich;
	NLTM_Which			mNegotiatedVersion;
	uint32_t			mSentFlags;			/* the flags we sent in first mst */
};	

static OSStatus _NtlmGeneratePasswordHashes(
	CFAllocatorRef alloc,
	NtlmGeneratorRef ntlm,
	CFStringRef password,
	CFDataRef* ntlmHash,
    CFDataRef* lmHash);
										  
/*
 * Validate type 2 message sent by the server; return interesting fields. 
 * NOTE we do not deal with the Context field here, which is only used
 * for local authetication.
 */
static OSStatus ntlmParseServerChallenge(
	CFDataRef		serverBlob,
	uint32_t		*serverFlags,		/* RETURNED */
	unsigned char   *challenge,			/* 8 bytes, mallocd by caller, RETURNED */
	unsigned char   **targetName,		/* mallocd and RETURNED */
	unsigned		*targetNameLen,		/* RETURNED */
	unsigned char   **targetInfo,		/* optionally mallocd and RETURNED */
	unsigned		*targetInfoLen)		/* optionally RETURNED */
{
	unsigned minLength;
	
	*targetName = NULL;
	*targetNameLen = 0;
	*targetInfo = NULL;
	*targetInfoLen = 0;
	
	if(serverBlob == NULL) {
		return NTLM_ERR_PARSE_ERR;
	}
	
	minLength = NTLM_SIGNATURE_LEN +
		(unsigned)sizeof(uint32_t) +			/* msg type */
		NTLM_SIZEOF_SEC_BUF +		/* target name */
		(unsigned)sizeof(uint32_t) +			/* flags */
		NTLM_CHALLENGE_LEN;		
	unsigned bufLen = (unsigned)CFDataGetLength(serverBlob);
	if(bufLen < minLength) {
		dprintf("ntlmParseServerChallenge: bad length\n");
		return NTLM_ERR_PARSE_ERR;
	}
	
	/* do not even think of touching serverBlob after this */
	const unsigned char *cp = CFDataGetBytePtr(serverBlob);
	
	/* byte 0: signature */
	if(memcmp(cp, NTLM_SIGNATURE, NTLM_SIGNATURE_LEN)) {
		dprintf("ntlmParseServerChallenge: signature mismatch\n");
		return NTLM_ERR_PARSE_ERR;
	}
	
	const unsigned char *currCp = cp + NTLM_SIGNATURE_LEN;
	
	/* byte 8: message type */
	uint32_t msgType = OSReadLittleInt32(currCp, 0);
	if(msgType != NTLM_MSG_MARKER_TYPE2) {
		dprintf("ntlmParseServerChallenge: bad msg type\n");
		return NTLM_ERR_PARSE_ERR;
	}
	currCp += sizeof(uint32_t);
	
	/* byte 12: target name, security buffer */
	const unsigned char *sbData;
	uint16_t sbLen;
	OSStatus ortn = ntlmParseSecBuffer(currCp, cp, bufLen, &sbData, &sbLen);
	if(ortn) {
		return ortn;
	}
	*targetName = (unsigned char *)malloc(sbLen);
	*targetNameLen = sbLen;
	memmove(*targetName, sbData, sbLen);
	currCp += NTLM_SIZEOF_SEC_BUF;
	
	/* byte 20: flags */
	*serverFlags = OSReadLittleInt32(currCp, 0);
	currCp += sizeof(uint32_t);
	
	/* byte 24: challenge */
	#if		DEBUG_FIXED_CHALLENGE
	memmove(challenge, fixServerChallenge, NTLM_CHALLENGE_LEN);
	#else
	memmove(challenge, currCp, NTLM_CHALLENGE_LEN);
	#endif
	currCp += NTLM_CHALLENGE_LEN;
	
	/* remaining fields optional */
	const unsigned char *endOfBuf = cp + bufLen;
	assert(endOfBuf >= currCp);
	if(endOfBuf == currCp) {
		return errSecSuccess;
	}
	
	if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) {
		/* not enough left for even one security buf; ignore */
		return errSecSuccess;
	}
	
	/* byte 32: context: skip */
	currCp += NTLM_SIZEOF_SEC_BUF;
	
	if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) {
		/* not enough left for target info security buf; ignore */
		return errSecSuccess;
	}
	
	/* byte 40: target info */
	ortn = ntlmParseSecBuffer(currCp, cp, bufLen, &sbData, &sbLen);
	if(ortn) {
		free(*targetName);
		*targetName = NULL;
		return ortn;
	}
	#if		DEBUG_FIXED_CHALLENGE
	sbData = fixTargetInfo;
	sbLen = sizeof(fixTargetInfo);
	#endif  /* DEBUG_FIXED_CHALLENGE */
	*targetInfo = (unsigned char *)malloc(sbLen);
	*targetInfoLen = sbLen;
	memmove(*targetInfo, sbData, sbLen);
	return errSecSuccess;
}

/* 
 * Create NTLMv2 responses (both NTLM and LM).
 */
static OSStatus ntlmGenerateNtlmV2Response(
	/* from app */
	CFStringRef		domain,		
	CFStringRef		userName,
	CFDataRef		ntlmHash,
	
	/* from server */
	const unsigned char *serverChallenge,
	const unsigned char *targetInfo,
	unsigned			targetInfoLen,
	
	/* returned */
	unsigned char	*lmV2Response,		// caller supplied, NTLM_LM_RESPONSE_LEN bytes
	unsigned char   **ntlmv2Response,   // mallocd and RETURNED
	unsigned		*ntlmV2ResponseLen) // RETURNED
{
	/* Random challenge used in both responses */
	unsigned char challenge[NTLM_CLIENT_NONCE_LEN];
	#if DEBUG_FIXED_CHALLENGE
	memmove(challenge, fixClientNonce, NTLM_CLIENT_NONCE_LEN);
	#else
	ntlmRand(NTLM_CLIENT_NONCE_LEN, challenge);
	#endif
	
	/* NTLM password hash */
	unsigned char ntlmPwdHash[NTLM_DIGEST_LENGTH];
//	ntlmPasswordHash(password, ntlmPwdHash);
	memmove(ntlmPwdHash, CFDataGetBytePtr(ntlmHash), sizeof(ntlmPwdHash));
	
	/* uppercase(userName | domain) */
	CFMutableStringRef userDomain = CFStringCreateMutableCopy(NULL, 0, userName);
	if(domain != NULL) {
		CFStringAppend(userDomain, domain);
	}
	CFStringUppercase(userDomain, NULL);
	
	/* declare some locals prior to any gotos */
	unsigned char *ucode = NULL;
	unsigned ucodeLen;
	unsigned char ntlmV2Hash[NTLM_DIGEST_LENGTH];
	unsigned char macText2[NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN];
	unsigned char challengeMac[NTLM_DIGEST_LENGTH];
	unsigned char blobMac[NTLM_DIGEST_LENGTH];
	unsigned char *ntlmv2Resp = NULL;
	CFMutableDataRef ntlmV2Blob = NULL;
	CFMutableDataRef catBlob = NULL;
	unsigned ntlmV2BlobLen;
	unsigned char blobSig[4] = {0x01, 0x01, 0x00, 0x00};
	
	/* HMAC(passwordHash, uppercase(userName | domain)) */
	ntlmStringToLE(userDomain, &ucode, &ucodeLen);
	OSStatus ortn = ntlmHmacMD5(ntlmPwdHash, NTLM_DIGEST_LENGTH,
		ucode, ucodeLen, ntlmV2Hash);
	if(ortn) {
		goto errOut;
	}
	
	/* HMAC(ntlmV2Hash, serverChallenge | clientChallenge) */
	memmove(macText2, serverChallenge, NTLM_CHALLENGE_LEN);
	memmove(macText2 + NTLM_CHALLENGE_LEN, challenge, NTLM_CLIENT_NONCE_LEN);
	ortn = ntlmHmacMD5(ntlmV2Hash, NTLM_DIGEST_LENGTH,
		macText2, NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN, challengeMac);
	if(ortn) {
		goto errOut;
	}

	/* LMv2 response := challengeMac | clientChallenge */
	memmove(lmV2Response, challengeMac, NTLM_DIGEST_LENGTH);
	memmove(lmV2Response + NTLM_DIGEST_LENGTH, challenge, NTLM_CLIENT_NONCE_LEN);
	
	/* Prepare the NTLMv2 'blob' */
	ntlmV2Blob = CFDataCreateMutable(NULL, 0);
	
	/* 0: 0x01010000 */
	CFDataAppendBytes(ntlmV2Blob, blobSig, 4);
	/* 4: reserved, zeroes */
	appendUint32(ntlmV2Blob, 0);
	/* 8: Timestamp */
	ntlmAppendTimestamp(ntlmV2Blob);
	/* 16: client challenge */
	CFDataAppendBytes(ntlmV2Blob, challenge, NTLM_CLIENT_NONCE_LEN);
	/* 24: unknown, zeroes */
	appendUint32(ntlmV2Blob, 0);
	/* 28: target info from server */
	CFDataAppendBytes(ntlmV2Blob, targetInfo, targetInfoLen);
	/* *: unknown, zeroes */
	appendUint32(ntlmV2Blob, 0);
	
	/* keep that blob; it'll go directly into the response. Now cook up 
	 * another one, the concatentation of the server challenge with the
	 * ntlmV2Blob */
	ntlmV2BlobLen = (unsigned)CFDataGetLength(ntlmV2Blob);
	catBlob = CFDataCreateMutable(NULL, 0);
	CFDataAppendBytes(catBlob, serverChallenge, NTLM_CHALLENGE_LEN);
	CFDataAppendBytes(catBlob, CFDataGetBytePtr(ntlmV2Blob), ntlmV2BlobLen);

	/* HMAC(ntlmV2Hash, serverChallenge | blob) */
	ortn = ntlmHmacMD5(ntlmV2Hash, NTLM_DIGEST_LENGTH,
		CFDataGetBytePtr(catBlob), (unsigned)CFDataGetLength(catBlob),
		blobMac);
	if(ortn) {
		goto errOut;
	}
	
	/* Finally, NTLMv2 response := (blobMac | ntlmV2Blob) */
	ntlmv2Resp = (unsigned char *)malloc(NTLM_DIGEST_LENGTH + ntlmV2BlobLen);
	memmove(ntlmv2Resp, blobMac, NTLM_DIGEST_LENGTH);
	memmove(ntlmv2Resp + NTLM_DIGEST_LENGTH, CFDataGetBytePtr(ntlmV2Blob), ntlmV2BlobLen);
	*ntlmv2Response = ntlmv2Resp;
	*ntlmV2ResponseLen = NTLM_DIGEST_LENGTH + ntlmV2BlobLen;
	ortn = errSecSuccess;
errOut:
	if(userDomain) {
		CFRelease(userDomain);
	}
	if(ntlmV2Blob) {
		CFRelease(ntlmV2Blob);
	}
	if(catBlob) {
		CFRelease(catBlob);
	}
	CFREE(ucode);
	return ortn;
}

/*
 * Create/release NtlmGenerator objects.
 */
OSStatus NtlmGeneratorCreate(
	NLTM_Which			which,
	NtlmGeneratorRef	*ntlmGen)			/* RETURNED */
{
	struct NtlmGenerator *gen = 
		(struct NtlmGenerator *)malloc(sizeof(struct NtlmGenerator));
	if(gen == NULL) {
		return errSecAllocate;
	}
	gen->mWhich = which;
	gen->mNegotiatedVersion = 0;			/* i.e., unknown */
	gen->mSentFlags = 0;
	*ntlmGen = gen;
	return errSecSuccess;
}
	
void NtlmGeneratorRelease(
	NtlmGeneratorRef	ntlmGen)
{
	if(ntlmGen == NULL) {
		return;
	}
	free(ntlmGen);
}

OSStatus NtlmCreateClientRequest(
	NtlmGeneratorRef	ntlmGen,
	CFDataRef			*clientRequest)		/* RETURNED */
{
	CFMutableDataRef req = CFDataCreateMutable(NULL, 0);
	if(req == NULL) {
		return errSecAllocate;
	}
	/* byte 0: signature, NULL terminated */
	CFDataAppendBytes(req, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN);
		 
	/* byte 8: message type */
	appendUint32(req, NTLM_MSG_MARKER_TYPE1);
	
	/* byte 12: the standard flags we send - we're wide open to all types */
	/* FIXME isn't there a way to tell the server we support NTLMv2? */
	ntlmGen->mSentFlags = NTLM_NegotiateUnicode |
		NTLM_NegotiateOEM |
		NTLM_RequestTarget |
		NTLM_NegotiateNTLM |
		NTLM_AlwaysSign;
	if(ntlmGen->mWhich & NW_NTLM2) {
		ntlmGen->mSentFlags |= NTLM_NegotiateNTLM2Key;
	}
	appendUint32(req, ntlmGen->mSentFlags);
	
	/* byte 16: optional supplied domain: not needed */
	CFIndex dex;
	appendSecBuf(req, 0, &dex);
		
	/* byte 24: optional supplied workstation: not needed */
	appendSecBuf(req, 0, &dex);

	*clientRequest = req;
	return errSecSuccess;
}
	
/* 
 * The meat & potatoes: given a server type 2 message, cook up a type 3 response. 
 */
OSStatus NtlmCreateClientResponse(
	NtlmGeneratorRef	ntlmGen,
	CFDataRef			serverBlob,
	CFStringRef			domain,				/* optional */
	CFStringRef			userName,
	CFStringRef			password,
	CFDataRef			*clientResponse)	/* RETURNED */
{
	CFDataRef ntlmHash = NULL;
	CFDataRef lmHash = NULL;
	OSStatus result = _NtlmGeneratePasswordHashes(kCFAllocatorDefault, ntlmGen, password, &ntlmHash, &lmHash);
	
	if (result == errSecSuccess) {

		result = _NtlmCreateClientResponse(ntlmGen, serverBlob, domain, userName, ntlmHash, lmHash, clientResponse);
	}
	
	if (ntlmHash)
		CFRelease(ntlmHash);
	
	if (lmHash)
		CFRelease(lmHash);
	
	return result;
}

OSStatus _NtlmCreateClientResponse(
	NtlmGeneratorRef	ntlmGen,
	CFDataRef			serverBlob,
	CFStringRef			domain,				/* optional */
	CFStringRef			userName,
	CFDataRef			ntlmHash,
	CFDataRef			lmHash,
	CFDataRef			*clientResponse)	/* RETURNED */
{
	OSStatus		ortn;
	uint32_t		serverFlags;
	unsigned char   serverChallenge[NTLM_CHALLENGE_LEN];  
	unsigned char   *targetName = NULL;
	unsigned		targetNameLen = 0;
	unsigned char   *targetInfo = NULL;
	unsigned		targetInfoLen = 0;
	CFIndex			lmRespOffset;
	unsigned char   lmResp[NTLM_LM_RESPONSE_LEN];
	CFIndex			ntlmRespOffset;
	unsigned char   ntlmResp[NTLM_LM_RESPONSE_LEN];
	unsigned char   *ntlmResponsePtr = NULL;
	unsigned		ntlmResponseLen = 0;
	unsigned char   *domainNameFlat = NULL;
	unsigned		domainNameFlatLen = 0;
	CFIndex			domainNameOffset;
	unsigned char   *userNameFlat = NULL;
	unsigned		userNameFlatLen = 0;
	CFIndex			userNameOffset;
	unsigned char   *workstationName = NULL;
	unsigned		workstationNameLen = 0;
	CFIndex			workstationNameOffset;
	CFIndex			nullDex;
	unsigned char   pwdHash[NTLM_DIGEST_LENGTH];
	
	ortn = ntlmParseServerChallenge(serverBlob, &serverFlags, serverChallenge,
		&targetName, &targetNameLen,
		&targetInfo, &targetInfoLen);
	if(ortn) {
		return ortn;
	}
	/* subsequent errors to errOut: */

	/* gather negotiated parameters */
	bool lm2Key  = (serverFlags & NTLM_NegotiateNTLM2Key) ? true : false;
	bool unicode = (serverFlags & NTLM_NegotiateUnicode) ? true : false;
	/* any others? */
	
	CFMutableDataRef clientBuf = CFDataCreateMutable(NULL, 0);
	if(clientBuf == NULL) {
		ortn = errSecAllocate;
		goto errOut;
	}
	
	if (domain) {
		domain = CFStringCreateMutableCopy(NULL, 0, domain);
		if (domain)
			CFStringUppercase((CFMutableStringRef)domain, NULL);
		else {
			ortn = errSecAllocate;
			goto errOut;
		}
	}
	
	/* byte 0: signature, NULL terminated */
	CFDataAppendBytes(clientBuf, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN);
		 
	/* byte 8: message type */
	appendUint32(clientBuf, NTLM_MSG_MARKER_TYPE3);
	
	/* LMv2 and NTLM responses */
	if( (targetInfo != NULL) &&							// server is NTLMv2 capable
	    (targetInfoLen != 0) &&							// ditto
		(serverFlags & NTLM_NegotiateTargetInfo) &&		// ditto
		(ntlmGen->mWhich & NW_NTLMv2) ) {				// ...and we are
		/*
		 * NTLMv2
		 */
		ortn = ntlmGenerateNtlmV2Response(domain, userName, ntlmHash,
			serverChallenge, targetInfo, targetInfoLen,
			lmResp, &ntlmResponsePtr, &ntlmResponseLen);
		if(ortn) {
			goto errOut;
		}
		
		/* 
		 * Write security buffers.
		 * 
		 * byte 12: LM response
		 * byte 20: NTLM response
		 */
		appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset);
		appendSecBuf(clientBuf, ntlmResponseLen, &ntlmRespOffset);
		ntlmGen->mNegotiatedVersion = NW_NTLMv2;
	}
	else {
		if(lm2Key && (ntlmGen->mWhich & NW_NTLM2)) {
			/* LM response: 8 random bytes, rest zeroes */
			#if DEBUG_FIXED_CHALLENGE
			memmove(lmResp, fixClientNonce, NTLM_CLIENT_NONCE_LEN);
			#else
			ntlmRand(NTLM_CLIENT_NONCE_LEN, lmResp);
			#endif
			memset(lmResp + NTLM_CLIENT_NONCE_LEN, 0, 
				NTLM_LM_RESPONSE_LEN - NTLM_CLIENT_NONCE_LEN);
			
			/* session nonce: server challenge | client nonce */
			unsigned char sessionNonce[NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN];
			memmove(sessionNonce, serverChallenge, NTLM_CHALLENGE_LEN);
			memmove(sessionNonce + NTLM_CHALLENGE_LEN, lmResp, NTLM_CLIENT_NONCE_LEN);
			
			/* NTLM2 session hash: the first 8 bytes of MD5(sessionNonce) */
			unsigned char sessionHash[NTLM_DIGEST_LENGTH];
			md5Hash(sessionNonce, NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN, sessionHash);
			
			/* standard password hash */
//			ntlmPasswordHash(password, pwdHash);
			memmove(pwdHash, CFDataGetBytePtr(ntlmHash), sizeof(pwdHash));
			
			/* NTLM response: DES with three different keys */
			ortn = lmv2Response(pwdHash, sessionHash, ntlmResp);
			if(ortn) {
				dprintf("***Error on ntlmResponse (3)\n");
				goto errOut;
			}
			ntlmGen->mNegotiatedVersion = NW_NTLM2;
		}
		else {
			dprintf("***NTLM protocol mismatch\n");
			ortn = NTLM_ERR_PROTOCOL_MISMATCH;
			goto errOut;
		
		}
		
		/* 
		 * Write security buffers.
		 * 
		 * byte 12: LM response
		 * byte 20: NTLM response
		 */
		appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset);
		appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &ntlmRespOffset);
		ntlmResponsePtr = ntlmResp;
		ntlmResponseLen = NTLM_LM_RESPONSE_LEN;
	}   /* not NTLMv2 */
	
	/* 
	 * convert domain and user as appropriate
	 * byte 28: domain (server) name
	 */
	if(domain != NULL) {
		ortn = ntlmStringFlatten(domain, unicode, &domainNameFlat, &domainNameFlatLen);
		if(ortn) {
			dprintf("createClientResponse: error converting domain name\n");
			ortn = NTLM_ERR_PARSE_ERR;
			goto errOut;
		}
	}
	appendSecBuf(clientBuf, domainNameFlatLen, &domainNameOffset);

	/* byte 36: user name */
	ortn = ntlmStringFlatten(userName, unicode, &userNameFlat, &userNameFlatLen);
	if(ortn) {
		dprintf("createClientResponse: error converting user name\n");
		ortn = NTLM_ERR_PARSE_ERR;
		goto errOut;
	}
	appendSecBuf(clientBuf, userNameFlatLen, &userNameOffset);
	
	/* byte 44: hostname */
	ortn = ntlmHostName(unicode, &workstationName, &workstationNameLen);
	if(ortn) {
		dprintf("createClientResponse: error getting host name\n");
		goto errOut;
	}
	appendSecBuf(clientBuf, workstationNameLen, &workstationNameOffset);
	
	/* byte 52: session key (whatever that is): optional, empty here */
	appendSecBuf(clientBuf, 0, &nullDex);
	
	/* byte 60: negotiated flags */
	appendUint32(clientBuf, ntlmGen->mSentFlags & serverFlags);

	/* finally, the data associated with the security buffers */
	secBufOffset(clientBuf, lmRespOffset);
	CFDataAppendBytes(clientBuf, lmResp, NTLM_LM_RESPONSE_LEN);

	secBufOffset(clientBuf, ntlmRespOffset);
	CFDataAppendBytes(clientBuf, ntlmResponsePtr, ntlmResponseLen);

	if(domain != NULL) {
		secBufOffset(clientBuf, domainNameOffset);
		CFDataAppendBytes(clientBuf, domainNameFlat, domainNameFlatLen);
	}
	
	secBufOffset(clientBuf, userNameOffset);
	CFDataAppendBytes(clientBuf, userNameFlat, userNameFlatLen);
	
	secBufOffset(clientBuf, workstationNameOffset);
	CFDataAppendBytes(clientBuf, workstationName, workstationNameLen);

errOut:
	CFREE(targetName);
	CFREE(targetInfo);
	CFREE(domainNameFlat);
	CFREE(userNameFlat);
	CFREE(workstationName);
	if (domain) CFRelease(domain);
	if(ntlmResponsePtr != ntlmResp) {
		/* i.e., it was mallocd by ntlmGenerateNtlmV2Response */
		CFREE(ntlmResponsePtr);
	}
	if(ortn == errSecSuccess) {
		*clientResponse = clientBuf;
	}
	else {
		if (clientBuf)
            CFRelease(clientBuf);
	}
	return ortn;
}
	
/* replacement for NtlmNegotiatedNtlm2: returns NW_NTLM2Only,
 * or NW_NTLMv2Only */
NLTM_Which NtlmGetNegotiatedVersion(
	NtlmGeneratorRef	ntlmGen)
{
	return ntlmGen->mNegotiatedVersion;
}

OSStatus _NtlmGeneratePasswordHashes(
	CFAllocatorRef alloc,
	NtlmGeneratorRef ntlm,
	CFStringRef password,
	CFDataRef* ntlmHash,
    CFDataRef* lmHash)
{
	OSStatus result = errSecSuccess;
	unsigned char hash[NTLM_DIGEST_LENGTH];
	
	result = ntlmPasswordHash(password, hash);
    if (result)
        return result;
	
	*ntlmHash = CFDataCreate(alloc, hash, sizeof(hash));
    memset(hash, 0, sizeof(hash));
    if (*ntlmHash == NULL)
        result = errSecAllocate;

    static const UInt8 zero[NTLM_DIGEST_LENGTH] = { 0 };
    *lmHash = CFDataCreate(NULL, zero, sizeof(zero));


	return result;
}

OSStatus NtlmGeneratePasswordHashes(
	CFAllocatorRef alloc,
	CFStringRef password,
	CFDataRef* ntlmHash,
	CFDataRef* lmHash)
{
	NtlmGeneratorRef ntlm = NULL;
	
	OSStatus result = NtlmGeneratorCreate(NW_Any, &ntlm);
	
	if (result == errSecSuccess) {
		result = _NtlmGeneratePasswordHashes(alloc, ntlm, password, ntlmHash, lmHash);
	}
	
	if (ntlm)
		NtlmGeneratorRelease(ntlm);
	
	return result;
}