]> git.saurik.com Git - apple/security.git/blame - ntlm/ntlmBlobPriv.c
Security-57337.40.85.tar.gz
[apple/security.git] / ntlm / ntlmBlobPriv.c
CommitLineData
d8f41ccd
A
1/*
2 * Copyright (c) 2000-2004,2006-2008,2010-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * ntlmBlobPriv.c - Private routines used by NtlmGenerator module.
26 */
27
28#include "ntlmBlobPriv.h"
29#include <Security/SecBase.h>
30
31#include <sys/types.h>
32#include <sys/uio.h>
33#include <unistd.h>
34#include <sys/param.h>
35#include <stdlib.h>
36#include <stdint.h>
37#include <assert.h>
38#include <fcntl.h>
39#include <ctype.h>
40#include <strings.h>
41#include <CommonCrypto/CommonDigest.h>
42#include <CommonCrypto/CommonCryptor.h>
43#include <CommonCrypto/CommonHMAC.h>
44#include <CoreFoundation/CFDate.h>
45#include <Security/SecFramework.h>
46#include <Security/SecRandom.h>
47#include <utilities/SecCFWrappers.h>
48
49#if DEBUG_FIXED_CHALLENGE
50/* Fixed 64-bit timestamp for sourceforge test vectors */
51static unsigned char dbgStamp[] =
52{
53 0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01
54};
55#endif /* DEBUG_FIXED_CHALLENGE */
56
57// MARK: -
58// MARK: Encode/Decode Routines
59
60/* write a 64-bit word, little endian */
61void appendUint64(
62 CFMutableDataRef buf,
63 uint64_t word)
64{
65#if 1
66 unsigned char cb[8];
67 OSWriteLittleInt64(cb, 0, word);
68 CFDataAppendBytes(buf, cb, 8);
69#else
70 /* This is an alternate implementation which may or may not be faster than
71 the above. */
72 CFIndex offset = CFDataGetLength(buf);
73 UInt8 *bytes = CFDataGetMutableBytePtr(buf);
74 CFDataIncreaseLength(buf, 8);
75 OSWriteLittleInt64(bytes, offset, word);
76#endif
77}
78
79/* write a 32-bit word, little endian */
80void appendUint32(
81 CFMutableDataRef buf,
82 uint32_t word)
83{
84#if 1
85 unsigned char cb[4];
86 OSWriteLittleInt32(cb, 0, word);
87 CFDataAppendBytes(buf, cb, 4);
88#else
89 /* This is an alternate implementation which may or may not be faster than
90 the above. */
91 CFIndex offset = CFDataGetLength(buf);
92 UInt8 *bytes = CFDataGetMutableBytePtr(buf);
93 CFDataIncreaseLength(buf, 4);
94 OSWriteLittleInt32(bytes, offset, word);
95#endif
96}
97
98/* write a 16-bit word, little endian */
99void appendUint16(
100 CFMutableDataRef buf,
101 uint16_t word)
102{
103 unsigned char cb[2];
104 OSWriteLittleInt16(cb, 0, word);
105 CFDataAppendBytes(buf, cb, 2);
106}
107
108/*
109 * Write a security buffer, providing the index into the CFData at which
110 * this security buffer's offset is located. Just before the actual data is written,
111 * go back and update the offset with the start of that data using secBufOffset().
112 */
113void appendSecBuf(
114 CFMutableDataRef buf,
115 uint16_t len,
116 CFIndex *offsetIndex)
117{
118#if 1
119 unsigned char cb[8];
120 OSWriteLittleInt16(cb, 0, len); /* buffer length */
121 OSWriteLittleInt16(cb, 2, len); /* buffer allocated size */
122 OSWriteLittleInt32(cb, 4, 0); /* offset is empty for now */
123 CFDataAppendBytes(buf, cb, 8);
124 *offsetIndex = CFDataGetLength(buf) - 4; /* offset will go here */
125#else
126 appendUint16(buf, len); /* buffer length */
127 appendUint16(buf, len); /* buffer allocated size */
128 *offsetIndex = CFDataGetLength(buf); /* offset will go here */
129 appendUint32(buf, 0); /* but it's empty for now */
130#endif
131}
132
133/*
134 * Update a security buffer's offset to be the current end of data in a CFData.
135 */
136void secBufOffset(
137 CFMutableDataRef buf,
138 CFIndex offsetIndex) /* obtained from appendSecBuf() */
139{
140 CFIndex currPos = CFDataGetLength(buf);
141 unsigned char cb[4];
142 OSWriteLittleInt32(cb, 0, (uint32_t)currPos);
143 CFRange range = {offsetIndex, 4};
144 CFDataReplaceBytes(buf, range, cb, 4);
145}
146
147/*
148 * Parse/validate a security buffer. Verifies that supplied offset/length don't go
149 * past end of avaialble data. Returns ptr to actual data and its length. Returns
150 * NTLM_ERR_PARSE_ERR on bogus values.
151 */
152OSStatus ntlmParseSecBuffer(
153 const unsigned char *cp, /* start of security buffer */
154 const unsigned char *bufStart, /* start of whole msg buffer */
155 unsigned bufLen, /* # of valid bytes starting at bufStart */
156 const unsigned char **data, /* RETURNED, start of actual data */
157 uint16_t *dataLen) /* RETURNED, length of actual data */
158{
159 assert(cp >= bufStart);
160
161 uint16_t secBufLen = OSReadLittleInt16(cp, 0);
162 /* skip length we just parsed plus alloc size, which we don't use */
163 cp += 4;
164 uint32_t offset = OSReadLittleInt32(cp, 0);
165 if((offset + secBufLen) > bufLen) {
166 dprintf("ntlmParseSecBuffer: buf overflow\n");
167 return NTLM_ERR_PARSE_ERR;
168 }
169 *data = bufStart + offset;
170 *dataLen = secBufLen;
171 return errSecSuccess;
172}
173
174// MARK: -
175// MARK: CFString Converters
176
177/*
178 * Convert CFString to little-endian unicode.
179 */
180void ntlmStringToLE(
181 CFStringRef pwd,
182 unsigned char **ucode, // mallocd and RETURNED
183 unsigned *ucodeLen) // RETURNED
184{
185 CFIndex len = CFStringGetLength(pwd);
186 unsigned char *data = (unsigned char *)malloc(len * 2);
187 unsigned char *cp = data;
188
189 CFIndex dex;
190 for(dex=0; dex<len; dex++) {
191 UniChar uc = CFStringGetCharacterAtIndex(pwd, dex);
192 *cp++ = uc & 0xff;
193 *cp++ = uc >> 8;
194 }
195 *ucode = data;
196 *ucodeLen = (unsigned)(len * 2);
197}
198
199/*
200 * Convert a CFStringRef into a mallocd array of chars suitable for the specified
201 * encoding. This might return an error if the string can't be converted
202 * appropriately.
203 */
204OSStatus ntlmStringFlatten(
205 CFStringRef str,
206 bool unicode,
207 unsigned char **flat, // mallocd and RETURNED
208 unsigned *flatLen) // RETURNED
209{
210 if(unicode) {
211 /* convert to little-endian unicode */
212 ntlmStringToLE(str, flat, flatLen);
213 return errSecSuccess;
214 }
215 else {
216 /* convert to ASCII C string */
217 CFIndex strLen = CFStringGetLength(str);
218 char *cStr = (char *)malloc(strLen + 1);
219 if(cStr == NULL) {
220 return errSecAllocate;
221 }
222 if(CFStringGetCString(str, cStr, strLen + 1, kCFStringEncodingASCII)) {
223 *flat = (unsigned char *)cStr;
224 *flatLen = (unsigned)strLen;
225 return errSecSuccess;
226 }
227
228 /*
229 * Well that didn't work. Try UTF8 - I don't know how a MS would behave if
230 * this portion of auth (only used for the LM response) didn't work.
231 */
232 dprintf("lmPasswordHash: ASCII password conversion failed; trying UTF8\n");
233 free(cStr);
234 cStr = (char *)malloc(strLen * 4);
235 if(cStr == NULL) {
236 return errSecAllocate;
237 }
e0e0d90e
A
238 CFDataRef dataFromCString = CFStringCreateExternalRepresentation(NULL, str, kCFStringEncodingUTF8, 0);
239 if(dataFromCString) {
d8f41ccd
A
240 *flat = (unsigned char *)cStr;
241 *flatLen = (unsigned)strLen;
e0e0d90e 242 CFReleaseNull(dataFromCString);
d8f41ccd
A
243 return errSecSuccess;
244 }
245 dprintf("lmPasswordHash: UTF8 password conversion failed\n");
246 free(cStr);
e0e0d90e 247 CFReleaseNull(dataFromCString);
d8f41ccd
A
248 return NTLM_ERR_PARSE_ERR;
249 }
250}
251
252// MARK: -
253// MARK: Machine Dependent Cruft
254
255/* random number generator */
256void ntlmRand(
257 unsigned len,
258 void *buf) /* allocated by caller, random data RETURNED */
259{
260 SecRandomCopyBytes(kSecRandomDefault, len, buf);
261}
262
263/* Obtain host name in appropriate encoding */
264OSStatus ntlmHostName(
265 bool unicode,
266 unsigned char **flat, // mallocd and RETURNED
267 unsigned *flatLen) // RETURNED
268{
269 char hostname[MAXHOSTNAMELEN];
270 if(gethostname(hostname, MAXHOSTNAMELEN)) {
271 #ifndef NDEBUG
272 perror("gethostname");
273 #endif
274 return errSecInternalComponent;
275 }
276 size_t len = strlen(hostname);
277 if(unicode) {
278 /* quickie "little endian unicode" conversion */
279 *flat = (unsigned char *)malloc(len * 2);
280 unsigned char *cp = *flat;
281 size_t dex;
282 for(dex=0; dex<len; dex++) {
283 *cp++ = hostname[dex];
284 *cp++ = 0;
285 }
286 *flatLen = (unsigned)len * 2;
287 return errSecSuccess;
288 }
289 else {
290 *flat = (unsigned char *)malloc(len);
291 *flatLen = (unsigned)len;
292 memmove(*flat, hostname, len);
293 return errSecSuccess;
294 }
295}
296
297/*
298 * Append 64-bit little-endiam timestamp to a CFData. Time is relative to
299 * January 1 1601, in tenths of a microsecond.
300 */
301
302CFGiblisGetSingleton(CFAbsoluteTime, ntlmGetBasis, ntlmBasisAbsoluteTime, ^{
303 *ntlmBasisAbsoluteTime = CFAbsoluteTimeForGregorianZuluDay(1601, 1, 1);
304});
305
306void ntlmAppendTimestamp(
307 CFMutableDataRef ntlmV2Blob)
308{
309 #if DEBUG_FIXED_CHALLENGE
310 /* Fixed 64-bit timestamp for sourceforge test vectors */
311 CFDataAppendBytes(ntlmV2Blob, dbgStamp, 8);
312 #else
313
314 CFAbsoluteTime nowTime = CFAbsoluteTimeGetCurrent();
315
316 /* elapsed := time in seconds since basis */
317 CFTimeInterval elapsed = nowTime - ntlmGetBasis();
318 /* now in tenths of microseconds */
319 elapsed *= 10000000.0;
320
321 appendUint64(ntlmV2Blob, (uint64_t)elapsed);
322 #endif
323}
324
325// MARK: -
326// MARK: Crypto
327
328/* MD4 and MD5 hash */
329#define NTLM_DIGEST_LENGTH 16
330void md4Hash(
331 const unsigned char *data,
332 unsigned dataLen,
333 unsigned char *digest) // caller-supplied, NTLM_DIGEST_LENGTH */
334{
335 CC_MD4_CTX ctx;
336 CC_MD4_Init(&ctx);
337 CC_MD4_Update(&ctx, data, dataLen);
338 CC_MD4_Final(digest, &ctx);
339}
340
341void md5Hash(
342 const unsigned char *data,
343 unsigned dataLen,
344 unsigned char *digest) // caller-supplied, NTLM_DIGEST_LENGTH */
345{
346 CC_MD5_CTX ctx;
347 CC_MD5_Init(&ctx);
348 CC_MD5_Update(&ctx, data, dataLen);
349 CC_MD5_Final(digest, &ctx);
350}
351
352/*
353 * Given 7 bytes, create 8-byte DES key. Our implementation ignores the
354 * parity bit (lsb), which simplifies this somewhat.
355 */
356void ntlmMakeDesKey(
357 const unsigned char *inKey, // 7 bytes
358 unsigned char *outKey) // 8 bytes
359{
360 outKey[0] = inKey[0] & 0xfe;
361 outKey[1] = ((inKey[0] << 7) | (inKey[1] >> 1)) & 0xfe;
362 outKey[2] = ((inKey[1] << 6) | (inKey[2] >> 2)) & 0xfe;
363 outKey[3] = ((inKey[2] << 5) | (inKey[3] >> 3)) & 0xfe;
364 outKey[4] = ((inKey[3] << 4) | (inKey[4] >> 4)) & 0xfe;
365 outKey[5] = ((inKey[4] << 3) | (inKey[5] >> 5)) & 0xfe;
366 outKey[6] = ((inKey[5] << 2) | (inKey[6] >> 6)) & 0xfe;
367 outKey[7] = (inKey[6] << 1) & 0xfe;
368}
369
370/*
371 * single block DES encrypt.
372 * This would really benefit from a DES implementation in CommonCrypto.
373 */
374OSStatus ntlmDesCrypt(
375 const unsigned char *key, // 8 bytes
376 const unsigned char *inData, // 8 bytes
377 unsigned char *outData) // 8 bytes
378{
379 size_t data_moved;
380 return CCCrypt(kCCEncrypt, kCCAlgorithmDES, 0, key, kCCKeySizeDES,
381 NULL /*no iv, 1 block*/, inData, 1 * kCCBlockSizeDES, outData,
382 1 * kCCBlockSizeDES, &data_moved);
383}
384
385/*
386 * HMAC/MD5.
387 */
388OSStatus ntlmHmacMD5(
389 const unsigned char *key,
390 unsigned keyLen,
391 const unsigned char *inData,
392 unsigned inDataLen,
393 unsigned char *mac) // caller provided, NTLM_DIGEST_LENGTH
394{
395 CCHmacContext hmac_md5_context;
396
397 CCHmacInit(&hmac_md5_context, kCCHmacAlgMD5, key, keyLen);
398 CCHmacUpdate(&hmac_md5_context, inData, inDataLen);
399 CCHmacFinal(&hmac_md5_context, mac);
400
401 return 0;
402}
403
404// MARK: -
405// MARK: LM and NTLM password and digest munging
406
407/*
408 * Calculate LM-style password hash. This really only works if the password
409 * is convertible to ASCII (that is, it will indeed return an error if that
410 * is not true).
411 *
412 * This is the most gawdawful constant I've ever seen in security-related code.
413 */
414static const unsigned char lmHashPlaintext[] = {'K', 'G', 'S', '!', '@', '#', '$', '%'};
415
416OSStatus lmPasswordHash(
417 CFStringRef pwd,
418 unsigned char *digest) // caller-supplied, NTLM_DIGEST_LENGTH
419{
420 /* convert to ASCII */
421 unsigned strLen;
422 unsigned char *cStr;
423 OSStatus ortn;
424 ortn = ntlmStringFlatten(pwd, false, &cStr, &strLen);
425 if(ortn) {
426 dprintf("lmPasswordHash: ASCII password conversion failed\n");
427 return ortn;
428 }
429
430 /* truncate/pad to 14 bytes and convert to upper case */
431 unsigned char pwdFix[NTLM_LM_PASSWORD_LEN];
432 unsigned toMove = NTLM_LM_PASSWORD_LEN;
433 if(strLen < NTLM_LM_PASSWORD_LEN) {
434 toMove = strLen;
435 }
436 memmove(pwdFix, cStr, toMove);
437 free(cStr);
438 unsigned dex;
439 for(dex=0; dex<NTLM_LM_PASSWORD_LEN; dex++) {
440 pwdFix[dex] = toupper(pwdFix[dex]);
441 }
442
443 /* two DES keys - raw material 7 bytes, munge to 8 bytes */
444 unsigned char desKey1[DES_KEY_SIZE], desKey2[DES_KEY_SIZE];
445 ntlmMakeDesKey(pwdFix, desKey1);
446 ntlmMakeDesKey(pwdFix + DES_RAW_KEY_SIZE, desKey2);
447
448 /* use each of those keys to encrypt the magic string */
449 ortn = ntlmDesCrypt(desKey1, lmHashPlaintext, digest);
450 if(ortn == errSecSuccess) {
451 ortn = ntlmDesCrypt(desKey2, lmHashPlaintext, digest + DES_BLOCK_SIZE);
452 }
453 return ortn;
454}
455
456/*
457 * Calculate NTLM password hash (MD4 on a unicode password).
458 */
459void ntlmPasswordHash(
460 CFStringRef pwd,
461 unsigned char *digest) // caller-supplied, NTLM_DIGEST_LENGTH
462{
463 unsigned char *data;
464 unsigned len;
465
466 /* convert to little-endian unicode */
467 ntlmStringToLE(pwd, &data, &len);
468 /* md4 hash of that */
469 md4Hash(data, len, digest);
470 free(data);
471}
472
473/*
474 * NTLM response: DES encrypt the challenge (or session hash) with three
475 * different keys derived from the password hash. Result is concatenation
476 * of three DES encrypts.
477 */
478#define ALL_KEYS_LENGTH (3 * DES_RAW_KEY_SIZE)
479OSStatus ntlmResponse(
480 const unsigned char *digest, // NTLM_DIGEST_LENGTH bytes
481 const unsigned char *ptext, // challenge or session hash
482 unsigned char *ntlmResp) // caller-supplied NTLM_LM_RESPONSE_LEN
483{
484 unsigned char allKeys[ALL_KEYS_LENGTH];
485 unsigned char key1[DES_KEY_SIZE], key2[DES_KEY_SIZE], key3[DES_KEY_SIZE];
486 OSStatus ortn;
487
488 memmove(allKeys, digest, NTLM_DIGEST_LENGTH);
489 memset(allKeys + NTLM_DIGEST_LENGTH, 0, ALL_KEYS_LENGTH - NTLM_DIGEST_LENGTH);
490 ntlmMakeDesKey(allKeys, key1);
491 ntlmMakeDesKey(allKeys + DES_RAW_KEY_SIZE, key2);
492 ntlmMakeDesKey(allKeys + (2 * DES_RAW_KEY_SIZE), key3);
493 ortn = ntlmDesCrypt(key1, ptext, ntlmResp);
494 if(ortn == errSecSuccess) {
495 ortn = ntlmDesCrypt(key2, ptext, ntlmResp + DES_BLOCK_SIZE);
496 }
497 if(ortn == errSecSuccess) {
498 ortn = ntlmDesCrypt(key3, ptext, ntlmResp + (2 * DES_BLOCK_SIZE));
499 }
500 return ortn;
501}
502