]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
1 | /* |
2 | * Copyright (c) 2000-2004,2006-2007,2011,2013 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 | * NtlmGenerator.c - NTLM client-side authentication engine. | |
26 | * | |
27 | * In the usual absence of documentation from Microsoft, the "inventors" of this | |
28 | * protocol, this module was written using the superb revers engineering documented | |
29 | * at | |
30 | * | |
31 | * http://davenport.sourceforge.net/ntlm.html#localAuthentication | |
32 | */ | |
33 | ||
34 | #include "NtlmGenerator.h" | |
35 | #include "ntlmBlobPriv.h" | |
36 | #include <Security/SecBase.h> | |
37 | ||
38 | #include <stdint.h> | |
39 | #include <stdio.h> | |
40 | #include <stdlib.h> | |
41 | #include <assert.h> | |
42 | #include <strings.h> | |
43 | ||
44 | /* | |
45 | * For debugging using fixed server challenge and client nonce. | |
46 | */ | |
47 | #if DEBUG_FIXED_CHALLENGE | |
48 | ||
49 | /* these are "test vectors", effectively, from sourceforge */ | |
50 | /* use pwd SecREt01, host/domain DOMAIN */ | |
51 | static const unsigned char fixServerChallenge[8] = | |
52 | { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; | |
53 | static const unsigned char fixClientNonce[8] = | |
54 | { 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44 }; | |
55 | ||
56 | static const unsigned char fixTargetInfo[] = { | |
57 | 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, | |
58 | 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, | |
59 | 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, | |
60 | 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, | |
61 | 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, | |
62 | 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, | |
63 | 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, | |
64 | 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, | |
65 | 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, | |
66 | 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, | |
67 | 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, | |
68 | 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, | |
69 | 0x00, 0x00 | |
70 | }; | |
71 | #endif | |
72 | ||
73 | /* app's NtlmGeneratorRef is a pointer to one of these */ | |
74 | struct NtlmGenerator { | |
75 | NLTM_Which mWhich; | |
76 | NLTM_Which mNegotiatedVersion; | |
77 | uint32_t mSentFlags; /* the flags we sent in first mst */ | |
78 | }; | |
79 | ||
80 | static OSStatus _NtlmGeneratePasswordHashes( | |
81 | CFAllocatorRef alloc, | |
82 | NtlmGeneratorRef ntlm, | |
83 | CFStringRef password, | |
84 | CFDataRef* ntlmHash, | |
85 | CFDataRef* lmHash); | |
86 | ||
87 | /* | |
88 | * Validate type 2 message sent by the server; return interesting fields. | |
89 | * NOTE we do not deal with the Context field here, which is only used | |
90 | * for local authetication. | |
91 | */ | |
92 | static OSStatus ntlmParseServerChallenge( | |
93 | CFDataRef serverBlob, | |
94 | uint32_t *serverFlags, /* RETURNED */ | |
95 | unsigned char *challenge, /* 8 bytes, mallocd by caller, RETURNED */ | |
96 | unsigned char **targetName, /* mallocd and RETURNED */ | |
97 | unsigned *targetNameLen, /* RETURNED */ | |
98 | unsigned char **targetInfo, /* optionally mallocd and RETURNED */ | |
99 | unsigned *targetInfoLen) /* optionally RETURNED */ | |
100 | { | |
101 | unsigned minLength; | |
102 | ||
103 | *targetName = NULL; | |
104 | *targetNameLen = 0; | |
105 | *targetInfo = NULL; | |
106 | *targetInfoLen = 0; | |
107 | ||
108 | if(serverBlob == NULL) { | |
109 | return NTLM_ERR_PARSE_ERR; | |
110 | } | |
111 | ||
112 | minLength = NTLM_SIGNATURE_LEN + | |
113 | (unsigned)sizeof(uint32_t) + /* msg type */ | |
114 | NTLM_SIZEOF_SEC_BUF + /* target name */ | |
115 | (unsigned)sizeof(uint32_t) + /* flags */ | |
116 | NTLM_CHALLENGE_LEN; | |
117 | unsigned bufLen = (unsigned)CFDataGetLength(serverBlob); | |
118 | if(bufLen < minLength) { | |
119 | dprintf("ntlmParseServerChallenge: bad length\n"); | |
120 | return NTLM_ERR_PARSE_ERR; | |
121 | } | |
122 | ||
123 | /* do not even think of touching serverBlob after this */ | |
124 | const unsigned char *cp = CFDataGetBytePtr(serverBlob); | |
125 | ||
126 | /* byte 0: signature */ | |
127 | if(memcmp(cp, NTLM_SIGNATURE, NTLM_SIGNATURE_LEN)) { | |
128 | dprintf("ntlmParseServerChallenge: signature mismatch\n"); | |
129 | return NTLM_ERR_PARSE_ERR; | |
130 | } | |
131 | ||
132 | const unsigned char *currCp = cp + NTLM_SIGNATURE_LEN; | |
133 | ||
134 | /* byte 8: message type */ | |
135 | uint32_t msgType = OSReadLittleInt32(currCp, 0); | |
136 | if(msgType != NTLM_MSG_MARKER_TYPE2) { | |
137 | dprintf("ntlmParseServerChallenge: bad msg type\n"); | |
138 | return NTLM_ERR_PARSE_ERR; | |
139 | } | |
140 | currCp += sizeof(uint32_t); | |
141 | ||
142 | /* byte 12: target name, security buffer */ | |
143 | const unsigned char *sbData; | |
144 | uint16_t sbLen; | |
145 | OSStatus ortn = ntlmParseSecBuffer(currCp, cp, bufLen, &sbData, &sbLen); | |
146 | if(ortn) { | |
147 | return ortn; | |
148 | } | |
149 | *targetName = (unsigned char *)malloc(sbLen); | |
150 | *targetNameLen = sbLen; | |
151 | memmove(*targetName, sbData, sbLen); | |
152 | currCp += NTLM_SIZEOF_SEC_BUF; | |
153 | ||
154 | /* byte 20: flags */ | |
155 | *serverFlags = OSReadLittleInt32(currCp, 0); | |
156 | currCp += sizeof(uint32_t); | |
157 | ||
158 | /* byte 24: challenge */ | |
159 | #if DEBUG_FIXED_CHALLENGE | |
160 | memmove(challenge, fixServerChallenge, NTLM_CHALLENGE_LEN); | |
161 | #else | |
162 | memmove(challenge, currCp, NTLM_CHALLENGE_LEN); | |
163 | #endif | |
164 | currCp += NTLM_CHALLENGE_LEN; | |
165 | ||
166 | /* remaining fields optional */ | |
167 | const unsigned char *endOfBuf = cp + bufLen; | |
168 | assert(endOfBuf >= currCp); | |
169 | if(endOfBuf == currCp) { | |
170 | return errSecSuccess; | |
171 | } | |
172 | ||
173 | if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) { | |
174 | /* not enough left for even one security buf; ignore */ | |
175 | return errSecSuccess; | |
176 | } | |
177 | ||
178 | /* byte 32: context: skip */ | |
179 | currCp += NTLM_SIZEOF_SEC_BUF; | |
180 | ||
181 | if(endOfBuf < (currCp + NTLM_SIZEOF_SEC_BUF)) { | |
182 | /* not enough left for target info security buf; ignore */ | |
183 | return errSecSuccess; | |
184 | } | |
185 | ||
186 | /* byte 40: target info */ | |
187 | ortn = ntlmParseSecBuffer(currCp, cp, bufLen, &sbData, &sbLen); | |
188 | if(ortn) { | |
189 | free(*targetName); | |
190 | *targetName = NULL; | |
191 | return ortn; | |
192 | } | |
193 | #if DEBUG_FIXED_CHALLENGE | |
194 | sbData = fixTargetInfo; | |
195 | sbLen = sizeof(fixTargetInfo); | |
196 | #endif /* DEBUG_FIXED_CHALLENGE */ | |
197 | *targetInfo = (unsigned char *)malloc(sbLen); | |
198 | *targetInfoLen = sbLen; | |
199 | memmove(*targetInfo, sbData, sbLen); | |
200 | return errSecSuccess; | |
201 | } | |
202 | ||
203 | /* | |
204 | * Create NTLMv2 responses (both NTLM and LM). | |
205 | */ | |
206 | static OSStatus ntlmGenerateNtlmV2Response( | |
207 | /* from app */ | |
208 | CFStringRef domain, | |
209 | CFStringRef userName, | |
210 | CFDataRef ntlmHash, | |
211 | ||
212 | /* from server */ | |
213 | const unsigned char *serverChallenge, | |
214 | const unsigned char *targetInfo, | |
215 | unsigned targetInfoLen, | |
216 | ||
217 | /* returned */ | |
218 | unsigned char *lmV2Response, // caller supplied, NTLM_LM_RESPONSE_LEN bytes | |
219 | unsigned char **ntlmv2Response, // mallocd and RETURNED | |
220 | unsigned *ntlmV2ResponseLen) // RETURNED | |
221 | { | |
222 | /* Random challenge used in both responses */ | |
223 | unsigned char challenge[NTLM_CLIENT_NONCE_LEN]; | |
224 | #if DEBUG_FIXED_CHALLENGE | |
225 | memmove(challenge, fixClientNonce, NTLM_CLIENT_NONCE_LEN); | |
226 | #else | |
227 | ntlmRand(NTLM_CLIENT_NONCE_LEN, challenge); | |
228 | #endif | |
229 | ||
230 | /* NTLM password hash */ | |
231 | unsigned char ntlmPwdHash[NTLM_DIGEST_LENGTH]; | |
232 | // ntlmPasswordHash(password, ntlmPwdHash); | |
233 | memmove(ntlmPwdHash, CFDataGetBytePtr(ntlmHash), sizeof(ntlmPwdHash)); | |
234 | ||
235 | /* uppercase(userName | domain) */ | |
236 | CFMutableStringRef userDomain = CFStringCreateMutableCopy(NULL, 0, userName); | |
237 | if(domain != NULL) { | |
238 | CFStringAppend(userDomain, domain); | |
239 | } | |
240 | CFStringUppercase(userDomain, NULL); | |
241 | ||
242 | /* declare some locals prior to any gotos */ | |
243 | unsigned char *ucode = NULL; | |
244 | unsigned ucodeLen; | |
245 | unsigned char ntlmV2Hash[NTLM_DIGEST_LENGTH]; | |
246 | unsigned char macText2[NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN]; | |
247 | unsigned char challengeMac[NTLM_DIGEST_LENGTH]; | |
248 | unsigned char blobMac[NTLM_DIGEST_LENGTH]; | |
249 | unsigned char *ntlmv2Resp = NULL; | |
250 | CFMutableDataRef ntlmV2Blob = NULL; | |
251 | CFMutableDataRef catBlob = NULL; | |
252 | unsigned ntlmV2BlobLen; | |
253 | unsigned char blobSig[4] = {0x01, 0x01, 0x00, 0x00}; | |
254 | ||
255 | /* HMAC(passwordHash, uppercase(userName | domain)) */ | |
256 | ntlmStringToLE(userDomain, &ucode, &ucodeLen); | |
257 | OSStatus ortn = ntlmHmacMD5(ntlmPwdHash, NTLM_DIGEST_LENGTH, | |
258 | ucode, ucodeLen, ntlmV2Hash); | |
259 | if(ortn) { | |
260 | goto errOut; | |
261 | } | |
262 | ||
263 | /* HMAC(ntlmV2Hash, serverChallenge | clientChallenge) */ | |
264 | memmove(macText2, serverChallenge, NTLM_CHALLENGE_LEN); | |
265 | memmove(macText2 + NTLM_CHALLENGE_LEN, challenge, NTLM_CLIENT_NONCE_LEN); | |
266 | ortn = ntlmHmacMD5(ntlmV2Hash, NTLM_DIGEST_LENGTH, | |
267 | macText2, NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN, challengeMac); | |
268 | if(ortn) { | |
269 | goto errOut; | |
270 | } | |
271 | ||
272 | /* LMv2 response := challengeMac | clientChallenge */ | |
273 | memmove(lmV2Response, challengeMac, NTLM_DIGEST_LENGTH); | |
274 | memmove(lmV2Response + NTLM_DIGEST_LENGTH, challenge, NTLM_CLIENT_NONCE_LEN); | |
275 | ||
276 | /* Prepare the NTLMv2 'blob' */ | |
277 | ntlmV2Blob = CFDataCreateMutable(NULL, 0); | |
278 | ||
279 | /* 0: 0x01010000 */ | |
280 | CFDataAppendBytes(ntlmV2Blob, blobSig, 4); | |
281 | /* 4: reserved, zeroes */ | |
282 | appendUint32(ntlmV2Blob, 0); | |
283 | /* 8: Timestamp */ | |
284 | ntlmAppendTimestamp(ntlmV2Blob); | |
285 | /* 16: client challenge */ | |
286 | CFDataAppendBytes(ntlmV2Blob, challenge, NTLM_CLIENT_NONCE_LEN); | |
287 | /* 24: unknown, zeroes */ | |
288 | appendUint32(ntlmV2Blob, 0); | |
289 | /* 28: target info from server */ | |
290 | CFDataAppendBytes(ntlmV2Blob, targetInfo, targetInfoLen); | |
291 | /* *: unknown, zeroes */ | |
292 | appendUint32(ntlmV2Blob, 0); | |
293 | ||
294 | /* keep that blob; it'll go directly into the response. Now cook up | |
295 | * another one, the concatentation of the server challenge with the | |
296 | * ntlmV2Blob */ | |
297 | ntlmV2BlobLen = (unsigned)CFDataGetLength(ntlmV2Blob); | |
298 | catBlob = CFDataCreateMutable(NULL, 0); | |
299 | CFDataAppendBytes(catBlob, serverChallenge, NTLM_CHALLENGE_LEN); | |
300 | CFDataAppendBytes(catBlob, CFDataGetBytePtr(ntlmV2Blob), ntlmV2BlobLen); | |
301 | ||
302 | /* HMAC(ntlmV2Hash, serverChallenge | blob) */ | |
303 | ortn = ntlmHmacMD5(ntlmV2Hash, NTLM_DIGEST_LENGTH, | |
304 | CFDataGetBytePtr(catBlob), (unsigned)CFDataGetLength(catBlob), | |
305 | blobMac); | |
306 | if(ortn) { | |
307 | goto errOut; | |
308 | } | |
309 | ||
310 | /* Finally, NTLMv2 response := (blobMac | ntlmV2Blob) */ | |
311 | ntlmv2Resp = (unsigned char *)malloc(NTLM_DIGEST_LENGTH + ntlmV2BlobLen); | |
312 | memmove(ntlmv2Resp, blobMac, NTLM_DIGEST_LENGTH); | |
313 | memmove(ntlmv2Resp + NTLM_DIGEST_LENGTH, CFDataGetBytePtr(ntlmV2Blob), ntlmV2BlobLen); | |
314 | *ntlmv2Response = ntlmv2Resp; | |
315 | *ntlmV2ResponseLen = NTLM_DIGEST_LENGTH + ntlmV2BlobLen; | |
316 | ortn = errSecSuccess; | |
317 | errOut: | |
318 | if(userDomain) { | |
319 | CFRelease(userDomain); | |
320 | } | |
321 | if(ntlmV2Blob) { | |
322 | CFRelease(ntlmV2Blob); | |
323 | } | |
324 | if(catBlob) { | |
325 | CFRelease(catBlob); | |
326 | } | |
327 | CFREE(ucode); | |
328 | return ortn; | |
329 | } | |
330 | ||
331 | /* | |
332 | * Create/release NtlmGenerator objects. | |
333 | */ | |
334 | OSStatus NtlmGeneratorCreate( | |
335 | NLTM_Which which, | |
336 | NtlmGeneratorRef *ntlmGen) /* RETURNED */ | |
337 | { | |
338 | struct NtlmGenerator *gen = | |
339 | (struct NtlmGenerator *)malloc(sizeof(struct NtlmGenerator)); | |
340 | if(gen == NULL) { | |
341 | return errSecAllocate; | |
342 | } | |
343 | gen->mWhich = which; | |
344 | gen->mNegotiatedVersion = 0; /* i.e., unknown */ | |
345 | gen->mSentFlags = 0; | |
346 | *ntlmGen = gen; | |
347 | return errSecSuccess; | |
348 | } | |
349 | ||
350 | void NtlmGeneratorRelease( | |
351 | NtlmGeneratorRef ntlmGen) | |
352 | { | |
353 | if(ntlmGen == NULL) { | |
354 | return; | |
355 | } | |
356 | free(ntlmGen); | |
357 | } | |
358 | ||
359 | OSStatus NtlmCreateClientRequest( | |
360 | NtlmGeneratorRef ntlmGen, | |
361 | CFDataRef *clientRequest) /* RETURNED */ | |
362 | { | |
363 | CFMutableDataRef req = CFDataCreateMutable(NULL, 0); | |
364 | if(req == NULL) { | |
365 | return errSecAllocate; | |
366 | } | |
367 | /* byte 0: signature, NULL terminated */ | |
368 | CFDataAppendBytes(req, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN); | |
369 | ||
370 | /* byte 8: message type */ | |
371 | appendUint32(req, NTLM_MSG_MARKER_TYPE1); | |
372 | ||
373 | /* byte 12: the standard flags we send - we're wide open to all types */ | |
374 | /* FIXME isn't there a way to tell the server we support NTLMv2? */ | |
375 | ntlmGen->mSentFlags = NTLM_NegotiateUnicode | | |
376 | NTLM_NegotiateOEM | | |
377 | NTLM_RequestTarget | | |
378 | NTLM_NegotiateNTLM | | |
379 | NTLM_AlwaysSign; | |
380 | if(ntlmGen->mWhich & NW_NTLM2) { | |
381 | ntlmGen->mSentFlags |= NTLM_NegotiateNTLM2Key; | |
382 | } | |
383 | appendUint32(req, ntlmGen->mSentFlags); | |
384 | ||
385 | /* byte 16: optional supplied domain: not needed */ | |
386 | CFIndex dex; | |
387 | appendSecBuf(req, 0, &dex); | |
388 | ||
389 | /* byte 24: optional supplied workstation: not needed */ | |
390 | appendSecBuf(req, 0, &dex); | |
391 | ||
392 | *clientRequest = req; | |
393 | return errSecSuccess; | |
394 | } | |
395 | ||
396 | /* | |
397 | * The meat & potatoes: given a server type 2 message, cook up a type 3 response. | |
398 | */ | |
399 | OSStatus NtlmCreateClientResponse( | |
400 | NtlmGeneratorRef ntlmGen, | |
401 | CFDataRef serverBlob, | |
402 | CFStringRef domain, /* optional */ | |
403 | CFStringRef userName, | |
404 | CFStringRef password, | |
405 | CFDataRef *clientResponse) /* RETURNED */ | |
406 | { | |
407 | CFDataRef ntlmHash = NULL; | |
408 | CFDataRef lmHash = NULL; | |
409 | OSStatus result = _NtlmGeneratePasswordHashes(kCFAllocatorDefault, ntlmGen, password, &ntlmHash, &lmHash); | |
410 | ||
411 | if (result == errSecSuccess) { | |
412 | ||
413 | result = _NtlmCreateClientResponse(ntlmGen, serverBlob, domain, userName, ntlmHash, lmHash, clientResponse); | |
414 | } | |
415 | ||
416 | if (ntlmHash) | |
417 | CFRelease(ntlmHash); | |
418 | ||
419 | if (lmHash) | |
420 | CFRelease(lmHash); | |
421 | ||
422 | return result; | |
423 | } | |
424 | ||
425 | OSStatus _NtlmCreateClientResponse( | |
426 | NtlmGeneratorRef ntlmGen, | |
427 | CFDataRef serverBlob, | |
428 | CFStringRef domain, /* optional */ | |
429 | CFStringRef userName, | |
430 | CFDataRef ntlmHash, | |
431 | CFDataRef lmHash, | |
432 | CFDataRef *clientResponse) /* RETURNED */ | |
433 | { | |
434 | OSStatus ortn; | |
435 | uint32_t serverFlags; | |
436 | unsigned char serverChallenge[NTLM_CHALLENGE_LEN]; | |
437 | unsigned char *targetName = NULL; | |
438 | unsigned targetNameLen = 0; | |
439 | unsigned char *targetInfo = NULL; | |
440 | unsigned targetInfoLen = 0; | |
441 | CFIndex lmRespOffset; | |
442 | unsigned char lmResp[NTLM_LM_RESPONSE_LEN]; | |
443 | CFIndex ntlmRespOffset; | |
444 | unsigned char ntlmResp[NTLM_LM_RESPONSE_LEN]; | |
445 | unsigned char *ntlmResponsePtr = NULL; | |
446 | unsigned ntlmResponseLen = 0; | |
447 | unsigned char *domainNameFlat = NULL; | |
448 | unsigned domainNameFlatLen = 0; | |
449 | CFIndex domainNameOffset; | |
450 | unsigned char *userNameFlat = NULL; | |
451 | unsigned userNameFlatLen = 0; | |
452 | CFIndex userNameOffset; | |
453 | unsigned char *workstationName = NULL; | |
454 | unsigned workstationNameLen = 0; | |
455 | CFIndex workstationNameOffset; | |
456 | CFIndex nullDex; | |
457 | unsigned char pwdHash[NTLM_DIGEST_LENGTH]; | |
458 | ||
459 | ortn = ntlmParseServerChallenge(serverBlob, &serverFlags, serverChallenge, | |
460 | &targetName, &targetNameLen, | |
461 | &targetInfo, &targetInfoLen); | |
462 | if(ortn) { | |
463 | return ortn; | |
464 | } | |
465 | /* subsequent errors to errOut: */ | |
466 | ||
467 | /* gather negotiated parameters */ | |
468 | bool lm2Key = (serverFlags & NTLM_NegotiateNTLM2Key) ? true : false; | |
469 | bool unicode = (serverFlags & NTLM_NegotiateUnicode) ? true : false; | |
470 | /* any others? */ | |
471 | ||
472 | CFMutableDataRef clientBuf = CFDataCreateMutable(NULL, 0); | |
473 | if(clientBuf == NULL) { | |
474 | ortn = errSecAllocate; | |
475 | goto errOut; | |
476 | } | |
477 | ||
478 | if (domain) { | |
479 | domain = CFStringCreateMutableCopy(NULL, 0, domain); | |
480 | if (domain) | |
481 | CFStringUppercase((CFMutableStringRef)domain, NULL); | |
482 | else { | |
483 | ortn = errSecAllocate; | |
484 | goto errOut; | |
485 | } | |
486 | } | |
487 | ||
488 | /* byte 0: signature, NULL terminated */ | |
489 | CFDataAppendBytes(clientBuf, (UInt8 *)NTLM_SIGNATURE, NTLM_SIGNATURE_LEN); | |
490 | ||
491 | /* byte 8: message type */ | |
492 | appendUint32(clientBuf, NTLM_MSG_MARKER_TYPE3); | |
493 | ||
494 | /* LM and NTLM responses */ | |
495 | if( (targetInfo != NULL) && // server is NTLMv2 capable | |
496 | (targetInfoLen != 0) && // ditto | |
497 | (serverFlags & NTLM_NegotiateTargetInfo) && // ditto | |
498 | (ntlmGen->mWhich & NW_NTLMv2) ) { // ...and we are | |
499 | /* | |
500 | * NTLMv2 | |
501 | */ | |
502 | ortn = ntlmGenerateNtlmV2Response(domain, userName, ntlmHash, | |
503 | serverChallenge, targetInfo, targetInfoLen, | |
504 | lmResp, &ntlmResponsePtr, &ntlmResponseLen); | |
505 | if(ortn) { | |
506 | goto errOut; | |
507 | } | |
508 | ||
509 | /* | |
510 | * Write security buffers. | |
511 | * | |
512 | * byte 12: LM response | |
513 | * byte 20: NTLM response | |
514 | */ | |
515 | appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset); | |
516 | appendSecBuf(clientBuf, ntlmResponseLen, &ntlmRespOffset); | |
517 | ntlmGen->mNegotiatedVersion = NW_NTLMv2; | |
518 | } | |
519 | else { | |
520 | if(lm2Key && (ntlmGen->mWhich & NW_NTLM2)) { | |
521 | /* LM response: 8 random bytes, rest zeroes */ | |
522 | #if DEBUG_FIXED_CHALLENGE | |
523 | memmove(lmResp, fixClientNonce, NTLM_CLIENT_NONCE_LEN); | |
524 | #else | |
525 | ntlmRand(NTLM_CLIENT_NONCE_LEN, lmResp); | |
526 | #endif | |
527 | memset(lmResp + NTLM_CLIENT_NONCE_LEN, 0, | |
528 | NTLM_LM_RESPONSE_LEN - NTLM_CLIENT_NONCE_LEN); | |
529 | ||
530 | /* session nonce: server challenge | client nonce */ | |
531 | unsigned char sessionNonce[NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN]; | |
532 | memmove(sessionNonce, serverChallenge, NTLM_CHALLENGE_LEN); | |
533 | memmove(sessionNonce + NTLM_CHALLENGE_LEN, lmResp, NTLM_CLIENT_NONCE_LEN); | |
534 | ||
535 | /* NTLM2 session hash: the first 8 bytes of MD5(sessionNonce) */ | |
536 | unsigned char sessionHash[NTLM_DIGEST_LENGTH]; | |
537 | md5Hash(sessionNonce, NTLM_CHALLENGE_LEN + NTLM_CLIENT_NONCE_LEN, sessionHash); | |
538 | ||
539 | /* standard password hash */ | |
540 | // ntlmPasswordHash(password, pwdHash); | |
541 | memmove(pwdHash, CFDataGetBytePtr(ntlmHash), sizeof(pwdHash)); | |
542 | ||
543 | /* NTLM response: DES with three different keys */ | |
544 | ortn = ntlmResponse(pwdHash, sessionHash, ntlmResp); | |
545 | if(ortn) { | |
546 | dprintf("***Error on ntlmResponse (3)\n"); | |
547 | goto errOut; | |
548 | } | |
549 | ntlmGen->mNegotiatedVersion = NW_NTLM2; | |
550 | } | |
551 | else if(ntlmGen->mWhich & NW_NTLM1) { | |
552 | /* | |
553 | * LM response - the old style 2-DES "password hash" applied | |
554 | * the the server's challenge | |
555 | */ | |
556 | // ortn = lmPasswordHash(password, pwdHash); | |
557 | // if(ortn) { | |
558 | // dprintf("***Error on lmPasswordHash\n"); | |
559 | // goto errOut; | |
560 | // } | |
561 | memmove(pwdHash, CFDataGetBytePtr(lmHash), sizeof(pwdHash)); | |
562 | ||
563 | ortn = ntlmResponse(pwdHash, serverChallenge, lmResp); | |
564 | if(ortn) { | |
565 | dprintf("***Error on ntlmResponse (1)\n"); | |
566 | goto errOut; | |
567 | } | |
568 | ||
569 | /* | |
570 | * NTLM response: md4 password hash, DES with three different keys | |
571 | */ | |
572 | // ntlmPasswordHash(password, pwdHash); | |
573 | memmove(pwdHash, CFDataGetBytePtr(ntlmHash), sizeof(pwdHash)); | |
574 | ||
575 | ortn = ntlmResponse(pwdHash, serverChallenge, ntlmResp); | |
576 | if(ortn) { | |
577 | dprintf("***Error on ntlmResponse (2)\n"); | |
578 | goto errOut; | |
579 | } | |
580 | ntlmGen->mNegotiatedVersion = NW_NTLM1; | |
581 | } | |
582 | else { | |
583 | dprintf("***NTLM protocol mismatch\n"); | |
584 | ortn = NTLM_ERR_PROTOCOL_MISMATCH; | |
585 | goto errOut; | |
586 | ||
587 | } | |
588 | ||
589 | /* | |
590 | * Write security buffers. | |
591 | * | |
592 | * byte 12: LM response | |
593 | * byte 20: NTLM response | |
594 | */ | |
595 | appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &lmRespOffset); | |
596 | appendSecBuf(clientBuf, NTLM_LM_RESPONSE_LEN, &ntlmRespOffset); | |
597 | ntlmResponsePtr = ntlmResp; | |
598 | ntlmResponseLen = NTLM_LM_RESPONSE_LEN; | |
599 | } /* not NTLMv2 */ | |
600 | ||
601 | /* | |
602 | * convert domain and user as appropriate | |
603 | * byte 28: domain (server) name | |
604 | */ | |
605 | if(domain != NULL) { | |
606 | ortn = ntlmStringFlatten(domain, unicode, &domainNameFlat, &domainNameFlatLen); | |
607 | if(ortn) { | |
608 | dprintf("createClientResponse: error converting domain name\n"); | |
609 | ortn = NTLM_ERR_PARSE_ERR; | |
610 | goto errOut; | |
611 | } | |
612 | } | |
613 | appendSecBuf(clientBuf, domainNameFlatLen, &domainNameOffset); | |
614 | ||
615 | /* byte 36: user name */ | |
616 | ortn = ntlmStringFlatten(userName, unicode, &userNameFlat, &userNameFlatLen); | |
617 | if(ortn) { | |
618 | dprintf("createClientResponse: error converting user name\n"); | |
619 | ortn = NTLM_ERR_PARSE_ERR; | |
620 | goto errOut; | |
621 | } | |
622 | appendSecBuf(clientBuf, userNameFlatLen, &userNameOffset); | |
623 | ||
624 | /* byte 44: hostname */ | |
625 | ortn = ntlmHostName(unicode, &workstationName, &workstationNameLen); | |
626 | if(ortn) { | |
627 | dprintf("createClientResponse: error getting host name\n"); | |
628 | goto errOut; | |
629 | } | |
630 | appendSecBuf(clientBuf, workstationNameLen, &workstationNameOffset); | |
631 | ||
632 | /* byte 52: session key (whatever that is): optional, empty here */ | |
633 | appendSecBuf(clientBuf, 0, &nullDex); | |
634 | ||
635 | /* byte 60: negotiated flags */ | |
636 | appendUint32(clientBuf, ntlmGen->mSentFlags & serverFlags); | |
637 | ||
638 | /* finally, the data associated with the security buffers */ | |
639 | secBufOffset(clientBuf, lmRespOffset); | |
640 | CFDataAppendBytes(clientBuf, lmResp, NTLM_LM_RESPONSE_LEN); | |
641 | ||
642 | secBufOffset(clientBuf, ntlmRespOffset); | |
643 | CFDataAppendBytes(clientBuf, ntlmResponsePtr, ntlmResponseLen); | |
644 | ||
645 | if(domain != NULL) { | |
646 | secBufOffset(clientBuf, domainNameOffset); | |
647 | CFDataAppendBytes(clientBuf, domainNameFlat, domainNameFlatLen); | |
648 | } | |
649 | ||
650 | secBufOffset(clientBuf, userNameOffset); | |
651 | CFDataAppendBytes(clientBuf, userNameFlat, userNameFlatLen); | |
652 | ||
653 | secBufOffset(clientBuf, workstationNameOffset); | |
654 | CFDataAppendBytes(clientBuf, workstationName, workstationNameLen); | |
655 | ||
656 | errOut: | |
657 | CFREE(targetName); | |
658 | CFREE(targetInfo); | |
659 | CFREE(domainNameFlat); | |
660 | CFREE(userNameFlat); | |
661 | CFREE(workstationName); | |
662 | if (domain) CFRelease(domain); | |
663 | if(ntlmResponsePtr != ntlmResp) { | |
664 | /* i.e., it was mallocd by ntlmGenerateNtlmV2Response */ | |
665 | CFREE(ntlmResponsePtr); | |
666 | } | |
667 | if(ortn == errSecSuccess) { | |
668 | *clientResponse = clientBuf; | |
669 | } | |
670 | else { | |
671 | if (clientBuf) | |
672 | CFRelease(clientBuf); | |
673 | } | |
674 | return ortn; | |
675 | } | |
676 | ||
677 | /* replacement for NtlmNegotiatedNtlm2: returns NW_NTLM1Only, NW_NTLM2Only, | |
678 | * or NW_NTLMv2Only */ | |
679 | NLTM_Which NtlmGetNegotiatedVersion( | |
680 | NtlmGeneratorRef ntlmGen) | |
681 | { | |
682 | return ntlmGen->mNegotiatedVersion; | |
683 | } | |
684 | ||
685 | OSStatus _NtlmGeneratePasswordHashes( | |
686 | CFAllocatorRef alloc, | |
687 | NtlmGeneratorRef ntlm, | |
688 | CFStringRef password, | |
689 | CFDataRef* ntlmHash, | |
690 | CFDataRef* lmHash) | |
691 | { | |
692 | OSStatus result = errSecSuccess; | |
693 | unsigned char hash[NTLM_DIGEST_LENGTH]; | |
694 | ||
695 | ntlmPasswordHash(password, hash); | |
696 | ||
697 | *ntlmHash = CFDataCreate(alloc, hash, sizeof(hash)); | |
698 | ||
699 | result = lmPasswordHash(password, hash); | |
700 | ||
701 | if (result == errSecSuccess) | |
702 | *lmHash = CFDataCreate(alloc, hash, sizeof(hash)); | |
703 | ||
704 | return result; | |
705 | } | |
706 | ||
707 | OSStatus NtlmGeneratePasswordHashes( | |
708 | CFAllocatorRef alloc, | |
709 | CFStringRef password, | |
710 | CFDataRef* ntlmHash, | |
711 | CFDataRef* lmHash) | |
712 | { | |
713 | NtlmGeneratorRef ntlm = NULL; | |
714 | ||
715 | OSStatus result = NtlmGeneratorCreate(NW_Any, &ntlm); | |
716 | ||
717 | if (result == errSecSuccess) { | |
718 | result = _NtlmGeneratePasswordHashes(alloc, ntlm, password, ntlmHash, lmHash); | |
719 | } | |
720 | ||
721 | if (ntlm) | |
722 | NtlmGeneratorRelease(ntlm); | |
723 | ||
724 | return result; | |
725 | } | |
726 |