]>
Commit | Line | Data |
---|---|---|
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 */ | |
51 | static 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 */ | |
61 | void 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 */ | |
80 | void 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 */ | |
99 | void 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 | */ | |
113 | void 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 | */ | |
136 | void 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 | */ | |
152 | OSStatus 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 | */ | |
180 | void 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 | */ | |
204 | OSStatus 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 */ | |
256 | void 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 */ | |
264 | OSStatus 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 | ||
302 | CFGiblisGetSingleton(CFAbsoluteTime, ntlmGetBasis, ntlmBasisAbsoluteTime, ^{ | |
303 | *ntlmBasisAbsoluteTime = CFAbsoluteTimeForGregorianZuluDay(1601, 1, 1); | |
304 | }); | |
305 | ||
306 | void 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 | |
330 | void 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 | ||
341 | void 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 | */ | |
356 | void 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 | */ | |
374 | OSStatus 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 | */ | |
388 | OSStatus 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 | */ | |
414 | static const unsigned char lmHashPlaintext[] = {'K', 'G', 'S', '!', '@', '#', '$', '%'}; | |
415 | ||
416 | OSStatus 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 | */ | |
459 | void 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) | |
479 | OSStatus 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 |