#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>
#if DEBUG_FIXED_CHALLENGE
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
struct NtlmGenerator {
NLTM_Which mWhich;
NLTM_Which mNegotiatedVersion;
uint32_t mSentFlags;
};
static OSStatus _NtlmGeneratePasswordHashes(
CFAllocatorRef alloc,
NtlmGeneratorRef ntlm,
CFStringRef password,
CFDataRef* ntlmHash,
CFDataRef* lmHash);
static OSStatus ntlmParseServerChallenge(
CFDataRef serverBlob,
uint32_t *serverFlags,
unsigned char *challenge,
unsigned char **targetName,
unsigned *targetNameLen,
unsigned char **targetInfo,
unsigned *targetInfoLen)
{
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) +
NTLM_SIZEOF_SEC_BUF +
(unsigned)sizeof(uint32_t) +
NTLM_CHALLENGE_LEN;
unsigned bufLen = (unsigned)CFDataGetLength(serverBlob);
if(bufLen < minLength) {
dprintf("ntlmParseServerChallenge: bad length\n");
return NTLM_ERR_PARSE_ERR;
}
const unsigned char *cp = CFDataGetBytePtr(serverBlob);
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;
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);
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;
*serverFlags = OSReadLittleInt32(currCp, 0);
currCp += sizeof(uint32_t);
#if DEBUG_FIXED_CHALLENGE
memmove(challenge, fixServerChallenge, NTLM_CHALLENGE_LEN);
#else
memmove(challenge, currCp, NTLM_CHALLENGE_LEN);
#endif
currCp += NTLM_CHALLENGE_LEN;
const unsigned char *endOfBuf = cp + bufLen;
assert(endOfBuf >= currCp);
if(endOfBuf == currCp) {
return errSecSuccess;
}
if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) {
return errSecSuccess;
}
currCp += NTLM_SIZEOF_SEC_BUF;
if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) {
return errSecSuccess;
}
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
*targetInfo = (unsigned char *)malloc(sbLen);
*targetInfoLen = sbLen;
memmove(*targetInfo, sbData, sbLen);
return errSecSuccess;
}
static OSStatus ntlmGenerateNtlmV2Response(
CFStringRef domain,
CFStringRef userName,
CFDataRef ntlmHash,
const unsigned char *serverChallenge,
const unsigned char *targetInfo,
unsigned targetInfoLen,
unsigned char *lmV2Response, unsigned char **ntlmv2Response, unsigned *ntlmV2ResponseLen) {
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
unsigned char ntlmPwdHash[NTLM_DIGEST_LENGTH];
memmove(ntlmPwdHash, CFDataGetBytePtr(ntlmHash), sizeof(ntlmPwdHash));
CFMutableStringRef userDomain = CFStringCreateMutableCopy(NULL, 0, userName);
if(domain != NULL) {
CFStringAppend(userDomain, domain);
}
CFStringUppercase(userDomain, NULL);
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};
ntlmStringToLE(userDomain, &ucode, &ucodeLen);
OSStatus ortn = ntlmHmacMD5(ntlmPwdHash, NTLM_DIGEST_LENGTH,
ucode, ucodeLen, ntlmV2Hash);
if(ortn) {
goto errOut;
}
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;
}
memmove(lmV2Response, challengeMac, NTLM_DIGEST_LENGTH);
memmove(lmV2Response + NTLM_DIGEST_LENGTH, challenge, NTLM_CLIENT_NONCE_LEN);
ntlmV2Blob = CFDataCreateMutable(NULL, 0);
CFDataAppendBytes(ntlmV2Blob, blobSig, 4);
appendUint32(ntlmV2Blob, 0);
ntlmAppendTimestamp(ntlmV2Blob);
CFDataAppendBytes(ntlmV2Blob, challenge, NTLM_CLIENT_NONCE_LEN);
appendUint32(ntlmV2Blob, 0);
CFDataAppendBytes(ntlmV2Blob, targetInfo, targetInfoLen);
appendUint32(ntlmV2Blob, 0);
ntlmV2BlobLen = (unsigned)CFDataGetLength(ntlmV2Blob);
catBlob = CFDataCreateMutable(NULL, 0);
CFDataAppendBytes(catBlob, serverChallenge, NTLM_CHALLENGE_LEN);
CFDataAppendBytes(catBlob, CFDataGetBytePtr(ntlmV2Blob), ntlmV2BlobLen);
ortn = ntlmHmacMD5(ntlmV2Hash, NTLM_DIGEST_LENGTH,
CFDataGetBytePtr(catBlob), (unsigned)CFDataGetLength(catBlob),
blobMac);
if(ortn) {
goto errOut;
}
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;
}
OSStatus NtlmGeneratorCreate(
NLTM_Which which,
NtlmGeneratorRef *ntlmGen)
{
struct NtlmGenerator *gen =
(struct NtlmGenerator *)malloc(sizeof(struct NtlmGenerator));
if(gen == NULL) {
return errSecAllocate;
}
gen->mWhich = which;
gen->mNegotiatedVersion = 0;
gen->mSentFlags = 0;
*ntlmGen = gen;
return errSecSuccess;
}
void NtlmGeneratorRelease(
NtlmGeneratorRef ntlmGen)
{
if(ntlmGen == NULL) {
return;
}
free(ntlmGen);
}
OSStatus NtlmCreateClientRequest(
NtlmGeneratorRef ntlmGen,
CFDataRef *clientRequest)
{
CFMutableDataRef req = CFDataCreateMutable(NULL, 0);
if(req == NULL) {
return errSecAllocate;
}
CFDataAppendBytes(req, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN);
appendUint32(req, NTLM_MSG_MARKER_TYPE1);
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);
CFIndex dex;
appendSecBuf(req, 0, &dex);
appendSecBuf(req, 0, &dex);
*clientRequest = req;
return errSecSuccess;
}
OSStatus NtlmCreateClientResponse(
NtlmGeneratorRef ntlmGen,
CFDataRef serverBlob,
CFStringRef domain,
CFStringRef userName,
CFStringRef password,
CFDataRef *clientResponse)
{
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,
CFStringRef userName,
CFDataRef ntlmHash,
CFDataRef lmHash,
CFDataRef *clientResponse)
{
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;
}
bool lm2Key = (serverFlags & NTLM_NegotiateNTLM2Key) ? true : false;
bool unicode = (serverFlags & NTLM_NegotiateUnicode) ? true : false;
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;
}
}
CFDataAppendBytes(clientBuf, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN);
appendUint32(clientBuf, NTLM_MSG_MARKER_TYPE3);
if( (targetInfo != NULL) && (targetInfoLen != 0) && (serverFlags & NTLM_NegotiateTargetInfo) && (ntlmGen->mWhich & NW_NTLMv2) ) {
ortn = ntlmGenerateNtlmV2Response(domain, userName, ntlmHash,
serverChallenge, targetInfo, targetInfoLen,
lmResp, &ntlmResponsePtr, &ntlmResponseLen);
if(ortn) {
goto errOut;
}
appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset);
appendSecBuf(clientBuf, ntlmResponseLen, &ntlmRespOffset);
ntlmGen->mNegotiatedVersion = NW_NTLMv2;
}
else {
if(lm2Key && (ntlmGen->mWhich & NW_NTLM2)) {
#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);
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);
unsigned char sessionHash[NTLM_DIGEST_LENGTH];
md5Hash(sessionNonce, NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN, sessionHash);
memmove(pwdHash, CFDataGetBytePtr(ntlmHash), sizeof(pwdHash));
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;
}
appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset);
appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &ntlmRespOffset);
ntlmResponsePtr = ntlmResp;
ntlmResponseLen = NTLM_LM_RESPONSE_LEN;
}
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);
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);
ortn = ntlmHostName(unicode, &workstationName, &workstationNameLen);
if(ortn) {
dprintf("createClientResponse: error getting host name\n");
goto errOut;
}
appendSecBuf(clientBuf, workstationNameLen, &workstationNameOffset);
appendSecBuf(clientBuf, 0, &nullDex);
appendUint32(clientBuf, ntlmGen->mSentFlags & serverFlags);
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) {
CFREE(ntlmResponsePtr);
}
if(ortn == errSecSuccess) {
*clientResponse = clientBuf;
}
else {
if (clientBuf)
CFRelease(clientBuf);
}
return ortn;
}
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;
}