#include "ntlmBlobPriv.h"
#include <CoreServices/CoreServices.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <sys/param.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <fcntl.h>
#include <ctype.h>
#include <strings.h>
#include <CommonCrypto/CommonDigest.h>
#include <Security/cssmapi.h>
#include <Security/cssmapple.h>
#include <CoreFoundation/CFDate.h>
#if DEBUG_FIXED_CHALLENGE
static unsigned char dbgStamp[] =
{
0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01
};
#endif
#pragma mark --- encode/decode routines ---
void serializeUint32(
uint32_t num,
unsigned char *buf)
{
buf[0] = num & 0xff;
buf[1] = num >> 8;
buf[2] = num >> 16;
buf[3] = num >> 24;
}
uint32_t deserializeUint32(
const unsigned char *buf)
{
uint32_t rtn = *buf++;
rtn |= ((uint32_t)(*buf++)) << 8;
rtn |= ((uint32_t)(*buf++)) << 16;
rtn |= ((uint32_t)(*buf)) << 24;
return rtn;
}
uint16_t deserializeUint16(
const unsigned char *buf)
{
uint16_t rtn = *buf++;
rtn |= ((uint16_t)(*buf)) << 8;
return rtn;
}
void appendUint32(
CFMutableDataRef buf,
uint32_t word)
{
unsigned char cb[4];
serializeUint32(word, cb);
CFDataAppendBytes(buf, cb, 4);
}
void appendUint16(
CFMutableDataRef buf,
uint16_t word)
{
unsigned char cb[2];
cb[0] = word & 0xff;
cb[1] = word >> 8;
CFDataAppendBytes(buf, cb, 2);
}
void appendSecBuf(
CFMutableDataRef buf,
uint16_t len,
CFIndex *offsetIndex)
{
appendUint16(buf, len);
appendUint16(buf, len);
*offsetIndex = CFDataGetLength(buf);
appendUint32(buf, 0);
}
void secBufOffset(
CFMutableDataRef buf,
CFIndex offsetIndex)
{
CFIndex currPos = CFDataGetLength(buf);
unsigned char cb[4];
serializeUint32((uint32_t)currPos, cb);
CFRange range = {offsetIndex, 4};
CFDataReplaceBytes(buf, range, cb, 4);
}
OSStatus ntlmParseSecBuffer(
const unsigned char *cp,
const unsigned char *bufStart,
unsigned bufLen,
const unsigned char **data,
uint16_t *dataLen)
{
assert(cp >= bufStart);
uint16_t secBufLen = deserializeUint16(cp);
cp += 4;
uint32_t offset = deserializeUint32(cp);
if((offset + secBufLen) > bufLen) {
dprintf("ntlmParseSecBuffer: buf overflow\n");
return NTLM_ERR_PARSE_ERR;
}
*data = bufStart + offset;
*dataLen = secBufLen;
return noErr;
}
#pragma mark --- CFString converters ---
void ntlmStringToLE(
CFStringRef pwd,
unsigned char **ucode, unsigned *ucodeLen) {
CFIndex len = CFStringGetLength(pwd);
unsigned char *data = (unsigned char *)malloc(len * 2);
unsigned char *cp = data;
for(CFIndex dex=0; dex<len; dex++) {
UniChar uc = CFStringGetCharacterAtIndex(pwd, dex);
*cp++ = uc & 0xff;
*cp++ = uc >> 8;
}
*ucode = data;
*ucodeLen = len * 2;
}
OSStatus ntlmStringFlatten(
CFStringRef str,
bool unicode,
unsigned char **flat, unsigned *flatLen) {
if(unicode) {
ntlmStringToLE(str, flat, flatLen);
return noErr;
}
else {
CFIndex strLen = CFStringGetLength(str);
char *cStr = (char *)malloc(strLen + 1);
if(cStr == NULL) {
return memFullErr;
}
if(CFStringGetCString(str, cStr, strLen + 1, kCFStringEncodingASCII)) {
*flat = (unsigned char *)cStr;
*flatLen = strLen;
return noErr;
}
dprintf("lmPasswordHash: ASCII password conversion failed; trying UTF8\n");
free(cStr);
cStr = (char *)malloc(strLen * 4);
if(cStr == NULL) {
return memFullErr;
}
if(CFStringCreateExternalRepresentation(NULL, str, kCFStringEncodingUTF8, 0)) {
*flat = (unsigned char *)cStr;
*flatLen = strLen;
return noErr;
}
dprintf("lmPasswordHash: UTF8 password conversion failed\n");
free(cStr);
return NTLM_ERR_PARSE_ERR;
}
}
#pragma mark --- machine dependent cruft ---
void ntlmRand(
unsigned len,
void *buf)
{
int fd = open("/dev/random", O_RDONLY, 0);
if(fd < 0) {
dprintf("***ntlmRand failed to open /dev/random\n");
return;
}
read(fd, buf, len);
close(fd);
}
OSStatus ntlmHostName(
bool unicode,
unsigned char **flat, unsigned *flatLen) {
char hostname[MAXHOSTNAMELEN];
if(gethostname(hostname, MAXHOSTNAMELEN)) {
#ifndef NDEBUG
perror("gethostname");
#endif
return internalComponentErr;
}
int len = strlen(hostname);
if(unicode) {
*flat = (unsigned char *)malloc(len * 2);
unsigned char *cp = *flat;
for(int dex=0; dex<len; dex++) {
*cp++ = hostname[dex];
*cp++ = 0;
}
*flatLen = len * 2;
return noErr;
}
else {
*flat = (unsigned char *)malloc(len);
*flatLen = len;
memmove(*flat, hostname, len);
return noErr;
}
}
static const CFGregorianDate ntlmTimeBasis =
{
1601, 1, 1, 0, 0, 0.0 };
void ntlmAppendTimestamp(
CFMutableDataRef ntlmV2Blob)
{
#if DEBUG_FIXED_CHALLENGE
CFDataAppendBytes(ntlmV2Blob, dbgStamp, 8);
#else
assert(CFGregorianDateIsValid(ntlmTimeBasis, kCFGregorianAllUnits));
CFAbsoluteTime basisTime = CFGregorianDateGetAbsoluteTime(ntlmTimeBasis, NULL);
CFAbsoluteTime nowTime = CFAbsoluteTimeGetCurrent();
CFTimeInterval elapsed = nowTime - basisTime;
elapsed *= 10000000.0;
uint32 lowWord = (uint32)elapsed;
elapsed /= 0x100000000ULL;
uint32 highWord = (uint32)elapsed;
appendUint32(ntlmV2Blob, lowWord);
appendUint32(ntlmV2Blob, highWord);
#endif
}
#pragma mark --- crypto ---
#define NTLM_DIGEST_LENGTH 16
void md4Hash(
const unsigned char *data,
unsigned dataLen,
unsigned char *digest) {
CC_MD4_CTX ctx;
CC_MD4_Init(&ctx);
CC_MD4_Update(&ctx, data, dataLen);
CC_MD4_Final(digest, &ctx);
}
void md5Hash(
const unsigned char *data,
unsigned dataLen,
unsigned char *digest) {
CC_MD5_CTX ctx;
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, data, dataLen);
CC_MD5_Final(digest, &ctx);
}
void ntlmMakeDesKey(
const unsigned char *inKey, unsigned char *outKey) {
outKey[0] = inKey[0] & 0xfe;
outKey[1] = ((inKey[0] << 7) | (inKey[1] >> 1)) & 0xfe;
outKey[2] = ((inKey[1] << 6) | (inKey[2] >> 2)) & 0xfe;
outKey[3] = ((inKey[2] << 5) | (inKey[3] >> 3)) & 0xfe;
outKey[4] = ((inKey[3] << 4) | (inKey[4] >> 4)) & 0xfe;
outKey[5] = ((inKey[4] << 3) | (inKey[5] >> 5)) & 0xfe;
outKey[6] = ((inKey[5] << 2) | (inKey[6] >> 6)) & 0xfe;
outKey[7] = (inKey[6] << 1) & 0xfe;
}
static void ntlmSetupKey(
CSSM_ALGORITHMS alg,
const unsigned char *keyData,
unsigned keyDataLen,
unsigned logicalKeySizeInBits,
CSSM_KEY_PTR ckey)
{
memset(ckey, 0, sizeof(*ckey));
CSSM_KEYHEADER &hdr = ckey->KeyHeader;
hdr.BlobType = CSSM_KEYBLOB_RAW;
hdr.Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
hdr.AlgorithmId = alg;
hdr.KeyClass = CSSM_KEYCLASS_SESSION_KEY;
hdr.LogicalKeySizeInBits = logicalKeySizeInBits;
hdr.KeyUsage = CSSM_KEYUSE_ANY;
ckey->KeyData.Data = (uint8 *)keyData;
ckey->KeyData.Length = keyDataLen;
}
OSStatus ntlmDesCrypt(
CSSM_CSP_HANDLE cspHand,
const unsigned char *key, const unsigned char *inData, const unsigned char *outData) {
CSSM_CC_HANDLE ccHand;
CSSM_RETURN crtn;
CSSM_KEY ckey;
ntlmSetupKey(CSSM_ALGID_DES, key, DES_KEY_SIZE,
DES_RAW_KEY_SIZE * 8, &ckey);
crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
CSSM_ALGID_DES,
CSSM_ALGMODE_ECB,
NULL, &ckey,
NULL, CSSM_PADDING_NONE,
NULL, &ccHand);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
#endif
return crtn;
}
CSSM_DATA ptext = {8, (uint8 *)inData};
CSSM_DATA ctext = {9, (uint8 *)outData};
uint32 bytesEncrypted;
crtn = CSSM_EncryptDataInit(ccHand);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_EncryptDataInit", crtn);
#endif
goto errOut;
}
crtn = CSSM_EncryptDataUpdate(ccHand,
&ptext, 1,
&ctext, 1,
&bytesEncrypted);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_EncryptDataUpdate", crtn);
#endif
}
errOut:
CSSM_DeleteContext(ccHand);
return crtn;
}
OSStatus ntlmHmacMD5(
CSSM_CSP_HANDLE cspHand,
const unsigned char *key,
unsigned keyLen,
const unsigned char *inData,
unsigned inDataLen,
unsigned char *mac) {
CSSM_CC_HANDLE ccHand;
CSSM_RETURN crtn;
CSSM_KEY ckey;
CSSM_DATA cdata = { inDataLen, (uint8 *)inData };
ntlmSetupKey(CSSM_ALGID_MD5HMAC, key, keyLen,
keyLen * 8, &ckey);
crtn = CSSM_CSP_CreateMacContext(cspHand,
CSSM_ALGID_MD5HMAC, &ckey, &ccHand);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_CSP_CreateMacContext", crtn);
#endif
return crtn;
}
crtn = CSSM_GenerateMacInit(ccHand);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_GenerateMacInit", crtn);
#endif
goto errOut;
}
crtn = CSSM_GenerateMacUpdate(ccHand, &cdata, 1);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_GenerateMacUpdate", crtn);
#endif
goto errOut;
}
cdata.Data = (uint8 *)mac;
cdata.Length = NTLM_DIGEST_LENGTH;
crtn = CSSM_GenerateMacFinal(ccHand, &cdata);
if(crtn) {
#ifndef NDEBUG
cssmPerror("CSSM_GenerateMacFinal", crtn);
#endif
}
errOut:
CSSM_DeleteContext(ccHand);
return crtn;
}
#pragma mark --- LM and NTLM password and digest munging ---
static const unsigned char lmHashPlaintext[] = {'K', 'G', 'S', '!', '@', '#', '$', '%'};
OSStatus lmPasswordHash(
CSSM_CSP_HANDLE cspHand,
CFStringRef pwd,
unsigned char *digest) {
unsigned strLen;
unsigned char *cStr;
OSStatus ortn;
ortn = ntlmStringFlatten(pwd, false, &cStr, &strLen);
if(ortn) {
dprintf("lmPasswordHash: ASCII password conversion failed\n");
return ortn;
}
unsigned char pwdFix[NTLM_LM_PASSWORD_LEN];
unsigned toMove = NTLM_LM_PASSWORD_LEN;
if(strLen < NTLM_LM_PASSWORD_LEN) {
toMove = strLen;
}
memmove(pwdFix, cStr, toMove);
free(cStr);
for(unsigned dex=0; dex<NTLM_LM_PASSWORD_LEN; dex++) {
pwdFix[dex] = toupper(pwdFix[dex]);
}
unsigned char desKey1[DES_KEY_SIZE], desKey2[DES_KEY_SIZE];
ntlmMakeDesKey(pwdFix, desKey1);
ntlmMakeDesKey(pwdFix + DES_RAW_KEY_SIZE, desKey2);
ortn = ntlmDesCrypt(cspHand, desKey1, lmHashPlaintext, digest);
if(ortn == noErr) {
ortn = ntlmDesCrypt(cspHand, desKey2, lmHashPlaintext, digest + DES_BLOCK_SIZE);
}
return ortn;
}
void ntlmPasswordHash(
CFStringRef pwd,
unsigned char *digest) {
unsigned char *data;
unsigned len;
ntlmStringToLE(pwd, &data, &len);
md4Hash(data, len, digest);
free(data);
}
#define ALL_KEYS_LENGTH (3 * DES_RAW_KEY_SIZE)
OSStatus ntlmResponse(
CSSM_CSP_HANDLE cspHand,
const unsigned char *digest, const unsigned char *ptext, unsigned char *ntlmResp) {
unsigned char allKeys[ALL_KEYS_LENGTH];
unsigned char key1[DES_KEY_SIZE], key2[DES_KEY_SIZE], key3[DES_KEY_SIZE];
OSStatus ortn;
memmove(allKeys, digest, NTLM_DIGEST_LENGTH);
memset(allKeys + NTLM_DIGEST_LENGTH, 0, ALL_KEYS_LENGTH - NTLM_DIGEST_LENGTH);
ntlmMakeDesKey(allKeys, key1);
ntlmMakeDesKey(allKeys + DES_RAW_KEY_SIZE, key2);
ntlmMakeDesKey(allKeys + (2 * DES_RAW_KEY_SIZE), key3);
ortn = ntlmDesCrypt(cspHand, key1, ptext, ntlmResp);
if(ortn == noErr) {
ortn = ntlmDesCrypt(cspHand, key2, ptext, ntlmResp + DES_BLOCK_SIZE);
}
if(ortn == noErr) {
ortn = ntlmDesCrypt(cspHand, key3, ptext, ntlmResp + (2 * DES_BLOCK_SIZE));
}
return ortn;
}