/* * Copyright (c) 2005 Apple Computer, 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@ */ /* * Copyright (c) 2000-2004 Apple Computer, 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@ */ /* * Private routines used by NtlmGenerator module. */ #include "ntlmBlobPriv.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DEBUG_FIXED_CHALLENGE /* Fixed 64-bit timestamp for sourceforge test vectors */ static unsigned char dbgStamp[] = { 0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01 }; #endif /* DEBUG_FIXED_CHALLENGE */ #pragma mark --- encode/decode routines --- /* uint32_t <--> unsigned char array */ 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; } /* write a 32-bit word, little endian */ void appendUint32( CFMutableDataRef buf, uint32_t word) { unsigned char cb[4]; serializeUint32(word, cb); CFDataAppendBytes(buf, cb, 4); } /* write a 16-bit word, little endian */ void appendUint16( CFMutableDataRef buf, uint16_t word) { unsigned char cb[2]; cb[0] = word & 0xff; cb[1] = word >> 8; CFDataAppendBytes(buf, cb, 2); } /* * Write a security buffer, providing the index into the CFData at which * this security buffer's offset is located. Just before the actual data is written, * go back and update the offset with the start of that data using secBufOffset(). */ void appendSecBuf( CFMutableDataRef buf, uint16_t len, CFIndex *offsetIndex) { appendUint16(buf, len); /* buffer length */ appendUint16(buf, len); /* buffer allocated size */ *offsetIndex = CFDataGetLength(buf); /* offset will go here */ appendUint32(buf, 0); /* but it's empty for now */ } /* * Update a security buffer's offset to be the current end of data in a CFData. */ void secBufOffset( CFMutableDataRef buf, CFIndex offsetIndex) /* obtained from appendSecBuf() */ { CFIndex currPos = CFDataGetLength(buf); unsigned char cb[4]; serializeUint32((uint32_t)currPos, cb); CFRange range = {offsetIndex, 4}; CFDataReplaceBytes(buf, range, cb, 4); } /* * Parse/validate a security buffer. Verifies that supplied offset/length don't go * past end of avaialble data. Returns ptr to actual data and its length. Returns * NTLM_ERR_PARSE_ERR on bogus values. */ OSStatus ntlmParseSecBuffer( const unsigned char *cp, /* start of security buffer */ const unsigned char *bufStart, /* start of whole msg buffer */ unsigned bufLen, /* # of valid bytes starting at bufStart */ const unsigned char **data, /* RETURNED, start of actual data */ uint16_t *dataLen) /* RETURNED, length of actual data */ { assert(cp >= bufStart); uint16_t secBufLen = deserializeUint16(cp); /* skip length we just parsed plus alloc size, which we don't use */ 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 --- /* * Convert CFString to little-endian unicode. */ void ntlmStringToLE( CFStringRef pwd, unsigned char **ucode, // mallocd and RETURNED unsigned *ucodeLen) // RETURNED { CFIndex len = CFStringGetLength(pwd); unsigned char *data = (unsigned char *)malloc(len * 2); unsigned char *cp = data; for(CFIndex dex=0; dex> 8; } *ucode = data; *ucodeLen = len * 2; } /* * Convert a CFStringRef into a mallocd array of chars suitable for the specified * encoding. This might return an error if the string can't be converted * appropriately. */ OSStatus ntlmStringFlatten( CFStringRef str, bool unicode, unsigned char **flat, // mallocd and RETURNED unsigned *flatLen) // RETURNED { if(unicode) { /* convert to little-endian unicode */ ntlmStringToLE(str, flat, flatLen); return noErr; } else { /* convert to ASCII C string */ 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; } /* * Well that didn't work. Try UTF8 - I don't know how a MS would behave if * this portion of auth (only used for the LM response) didn't work. */ 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 --- /* random number generator */ void ntlmRand( unsigned len, void *buf) /* allocated by caller, random data RETURNED */ { 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); } /* Obtain host name in appropriate encoding */ OSStatus ntlmHostName( bool unicode, unsigned char **flat, // mallocd and RETURNED unsigned *flatLen) // RETURNED { char hostname[MAXHOSTNAMELEN]; if(gethostname(hostname, MAXHOSTNAMELEN)) { #ifndef NDEBUG perror("gethostname"); #endif return internalComponentErr; } int len = strlen(hostname); if(unicode) { /* quickie "little endian unicode" conversion */ *flat = (unsigned char *)malloc(len * 2); unsigned char *cp = *flat; for(int dex=0; dex> 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, /* in bytes */ 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; } /* * single block DES encrypt. * This would really benefit from a DES implementation in CommonCrypto. */ OSStatus ntlmDesCrypt( CSSM_CSP_HANDLE cspHand, const unsigned char *key, // 8 bytes const unsigned char *inData, // 8 bytes const unsigned char *outData) // 8 bytes { 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, // access cred &ckey, NULL, // InitVector CSSM_PADDING_NONE, NULL, // Params &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; } /* * HMAC/MD5. */ OSStatus ntlmHmacMD5( CSSM_CSP_HANDLE cspHand, const unsigned char *key, unsigned keyLen, const unsigned char *inData, unsigned inDataLen, unsigned char *mac) // caller provided, NTLM_DIGEST_LENGTH { 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; } /* provide pre-allocated output buffer */ 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 --- /* * Calculate LM-style password hash. This really only works if the password * is convertible to ASCII (that is, it will indeed return an error if that * is not true). * * This is the most gawdawful constant I've ever seen in security-related code. */ static const unsigned char lmHashPlaintext[] = {'K', 'G', 'S', '!', '@', '#', '$', '%'}; OSStatus lmPasswordHash( CSSM_CSP_HANDLE cspHand, CFStringRef pwd, unsigned char *digest) // caller-supplied, NTLM_DIGEST_LENGTH { /* convert to ASCII */ unsigned strLen; unsigned char *cStr; OSStatus ortn; ortn = ntlmStringFlatten(pwd, false, &cStr, &strLen); if(ortn) { dprintf("lmPasswordHash: ASCII password conversion failed\n"); return ortn; } /* truncate/pad to 14 bytes and convert to upper case */ 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