Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

591 lines
14 KiB

  1. /*++
  2. Copyright (c) 1987-1996 Microsoft Corporation
  3. Module Name:
  4. joincrypt.c
  5. Abstract:
  6. Authentication related functions required by netjoin
  7. Author:
  8. kumarp 29-May-1999
  9. Notes:
  10. The functions in this file used to be made avaialbe by
  11. including net\svcdlls\logonsrv\server\ssiauth.c. This led to several
  12. problems due to which, the functions are now copied from that file
  13. into this separate file.
  14. --*/
  15. #pragma hdrstop
  16. #define WKSTA_NETLOGON
  17. #define NETSETUP_JOIN
  18. #include <netsetp.h>
  19. #include <crypt.h>
  20. #include <ntsam.h>
  21. #include <logonmsv.h>
  22. #include <lmshare.h>
  23. #include <wincrypt.h>
  24. #include <netlogon.h>
  25. #include <logonp.h>
  26. #include <logonmsv.h>
  27. #include <ssi.h>
  28. #include <wchar.h>
  29. #include "joinp.h"
  30. LONG NlGlobalSessionCounter = 0;
  31. HCRYPTPROV NlGlobalCryptProvider = (HCRYPTPROV)NULL;
  32. #define NlPrint(x)
  33. #define NlpDumpBuffer(x,y,z)
  34. BOOLEAN
  35. NlGenerateRandomBits(
  36. PUCHAR Buffer,
  37. ULONG BufferLen
  38. );
  39. #define NlQuerySystemTime( _Time ) GetSystemTimeAsFileTime( (LPFILETIME)(_Time) )
  40. VOID
  41. NlComputeChallenge(
  42. OUT PNETLOGON_CREDENTIAL Challenge
  43. );
  44. VOID
  45. NlComputeCredentials(
  46. IN PNETLOGON_CREDENTIAL Challenge,
  47. OUT PNETLOGON_CREDENTIAL Credential,
  48. IN PNETLOGON_SESSION_KEY SessionKey
  49. );
  50. NTSTATUS
  51. NlMakeSessionKey(
  52. IN ULONG NegotiatedFlags,
  53. IN PNT_OWF_PASSWORD CryptKey,
  54. IN PNETLOGON_CREDENTIAL ClientChallenge,
  55. IN PNETLOGON_CREDENTIAL ServerChallenge,
  56. OUT PNETLOGON_SESSION_KEY SessionKey
  57. )
  58. /*++
  59. Routine Description:
  60. Build an encryption key for use in authentication for
  61. this RequestorName.
  62. Arguments:
  63. NegotiatedFlags - Determines the strength of the key.
  64. CryptKey -- The OWF password of the user account being used.
  65. ClientChallenge -- 8 byte (64 bit) number generated by caller
  66. ServerChallenge -- 8 byte (64 bit) number generated by primary
  67. SessionKey -- 16 byte (128 bit) number generated at both ends
  68. If the key strength is weak, the last 64 bits will be zero.
  69. Return Value:
  70. TRUE: Success
  71. FALSE: Failure
  72. NT status code.
  73. --*/
  74. {
  75. NTSTATUS Status;
  76. BLOCK_KEY BlockKey;
  77. NETLOGON_SESSION_KEY TempSessionKey;
  78. #ifndef NETSETUP_JOIN
  79. PCHECKSUM_BUFFER CheckBuffer = NULL;
  80. PCHECKSUM_FUNCTION Check;
  81. #endif // NETSETUP_JOIN
  82. //
  83. // Start with a zero key
  84. //
  85. RtlZeroMemory(SessionKey, sizeof(NETLOGON_SESSION_KEY));
  86. #ifdef NETSETUP_JOIN
  87. UNREFERENCED_PARAMETER( NegotiatedFlags );
  88. #else // NETSETUP_JOIN
  89. //
  90. // If the caller wants a strong key,
  91. // Compute it.
  92. //
  93. if ( NegotiatedFlags & NETLOGON_SUPPORTS_STRONG_KEY ) {
  94. // PCRYPTO_SYSTEM CryptSystem;
  95. UCHAR LocalChecksum[sizeof(*SessionKey)];
  96. // ULONG OutputSize;
  97. //
  98. // Initialize the checksum routines.
  99. //
  100. Status = CDLocateCheckSum( KERB_CHECKSUM_MD5_HMAC, &Check);
  101. if (!NT_SUCCESS(Status)) {
  102. NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to load checksum routines: 0x%x\n", Status));
  103. goto Cleanup;
  104. }
  105. ASSERT(Check->CheckSumSize <= sizeof(LocalChecksum));
  106. Status = Check->InitializeEx(
  107. (LPBYTE)CryptKey,
  108. sizeof( *CryptKey ),
  109. 0, // no message type
  110. &CheckBuffer );
  111. if (!NT_SUCCESS(Status)) {
  112. NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to initialize checksum routines: 0x%x\n", Status));
  113. goto Cleanup;
  114. }
  115. //
  116. // Sum in the client challenge, a constant, and the server challenge
  117. //
  118. Check->Sum( CheckBuffer,
  119. sizeof(*ClientChallenge),
  120. (PUCHAR)ClientChallenge );
  121. Check->Sum( CheckBuffer,
  122. sizeof(*ServerChallenge),
  123. (PUCHAR)ServerChallenge );
  124. //
  125. // Finish the checksum
  126. //
  127. (void) Check->Finalize(CheckBuffer, LocalChecksum);
  128. //
  129. // Copy the checksum into the message.
  130. //
  131. ASSERT( sizeof(LocalChecksum) >= sizeof(*SessionKey) );
  132. RtlCopyMemory( SessionKey, LocalChecksum, sizeof(*SessionKey) );
  133. //
  134. // Compute weaker (but backward compatible key)
  135. //
  136. } else {
  137. #endif // NETSETUP_JOIN
  138. //
  139. // we will have a 128 bit key (64 bit encrypted rest padded with 0s)
  140. //
  141. // SessionKey = C + P (arithmetic sum ignore carry)
  142. //
  143. *((unsigned long * ) SessionKey) =
  144. *((unsigned long * ) ClientChallenge) +
  145. *((unsigned long * ) ServerChallenge);
  146. *((unsigned long * )((LPBYTE)SessionKey + 4)) =
  147. *((unsigned long * )((LPBYTE)ClientChallenge + 4)) +
  148. *((unsigned long * )((LPBYTE)ServerChallenge + 4));
  149. //
  150. // CryptKey is our 16 byte key to be used as described in codespec
  151. // use first 7 bytes of CryptKey for first encryption
  152. //
  153. RtlCopyMemory( &BlockKey, CryptKey, BLOCK_KEY_LENGTH );
  154. Status = RtlEncryptBlock(
  155. (PCLEAR_BLOCK) SessionKey, // Clear text
  156. &BlockKey, // Key
  157. (PCYPHER_BLOCK) &TempSessionKey); // Cypher Block
  158. if ( !NT_SUCCESS( Status ) ) {
  159. goto Cleanup;
  160. }
  161. //
  162. // Further encrypt the encrypted "SessionKey" using upper 7 bytes
  163. //
  164. ASSERT( LM_OWF_PASSWORD_LENGTH == 2*BLOCK_KEY_LENGTH+2 );
  165. RtlCopyMemory( &BlockKey,
  166. ((PUCHAR)CryptKey) + 2 + BLOCK_KEY_LENGTH,
  167. BLOCK_KEY_LENGTH );
  168. Status = RtlEncryptBlock(
  169. (PCLEAR_BLOCK) &TempSessionKey, // Clear text
  170. &BlockKey, // Key
  171. (PCYPHER_BLOCK) SessionKey); // Cypher Block
  172. if ( !NT_SUCCESS( Status ) ) {
  173. goto Cleanup;
  174. }
  175. #ifndef NETSETUP_JOIN
  176. }
  177. #endif // NETSETUP_JOIN
  178. Cleanup:
  179. #ifndef NETSETUP_JOIN
  180. if (CheckBuffer != NULL) {
  181. Status = Check->Finish(&CheckBuffer);
  182. if (!NT_SUCCESS(Status)) {
  183. NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to finish checksum: 0x%x\n", Status));
  184. }
  185. }
  186. #endif // NETSETUP_JOIN
  187. return Status;
  188. }
  189. VOID
  190. NlComputeChallenge(
  191. OUT PNETLOGON_CREDENTIAL Challenge
  192. )
  193. /*++
  194. Routine Description:
  195. Generates a 64 bit challenge
  196. Arguments:
  197. Challenge - Returns the computed challenge
  198. Return Value:
  199. None.
  200. --*/
  201. {
  202. //
  203. // Use an ideal random bit generator.
  204. //
  205. if (!NlGenerateRandomBits( (LPBYTE)Challenge, sizeof(*Challenge) )) {
  206. NlPrint((NL_CRITICAL, "Can't NlGenerateRandomBits\n" ));
  207. }
  208. return;
  209. }
  210. VOID
  211. NlComputeCredentials(
  212. IN PNETLOGON_CREDENTIAL Challenge,
  213. OUT PNETLOGON_CREDENTIAL Credential,
  214. IN PNETLOGON_SESSION_KEY SessionKey
  215. )
  216. /*++
  217. Routine Description:
  218. Calculate the credentials by encrypting the 8 byte
  219. challenge with first 7 bytes of sessionkey and then
  220. further encrypting it by next 7 bytes of sessionkey.
  221. Arguments:
  222. Challenge - Supplies the 8 byte (64 bit) challenge
  223. Credential - Returns the 8 byte (64 bit) number generated
  224. SessionKey - Supplies 14 byte (112 bit) encryption key
  225. The buffer is 16 bytes (128 bits) long. For a weak key, the trailing 8 bytes
  226. are zero. For a strong key, this routine ingored that trailing 2 bytes of
  227. useful key.
  228. Return Value:
  229. NONE
  230. --*/
  231. {
  232. NTSTATUS Status;
  233. BLOCK_KEY BlockKey;
  234. CYPHER_BLOCK IntermediateBlock;
  235. RtlZeroMemory(Credential, sizeof(*Credential));
  236. //
  237. // use first 7 bytes of SessionKey for first encryption
  238. //
  239. RtlCopyMemory( &BlockKey, SessionKey, BLOCK_KEY_LENGTH );
  240. Status = RtlEncryptBlock( (PCLEAR_BLOCK) Challenge, // Cleartext
  241. &BlockKey, // Key
  242. &IntermediateBlock ); // Cypher Block
  243. ASSERT( NT_SUCCESS(Status) );
  244. //
  245. // further encrypt the encrypted Credential using next 7 bytes
  246. //
  247. RtlCopyMemory( &BlockKey,
  248. ((PUCHAR)SessionKey) + BLOCK_KEY_LENGTH,
  249. BLOCK_KEY_LENGTH );
  250. Status = RtlEncryptBlock( (PCLEAR_BLOCK) &IntermediateBlock, // Cleartext
  251. &BlockKey, // Key
  252. Credential ); // Cypher Block
  253. ASSERT( NT_SUCCESS(Status) );
  254. return;
  255. }
  256. BOOLEAN
  257. NlGenerateRandomBits(
  258. PUCHAR Buffer,
  259. ULONG BufferLen
  260. )
  261. /*++
  262. Routine Description:
  263. Generates random bits
  264. Arguments:
  265. pBuffer - Buffer to fill
  266. cbBuffer - Number of bytes in buffer
  267. Return Value:
  268. Status of the operation.
  269. --*/
  270. {
  271. if( !CryptGenRandom( NlGlobalCryptProvider, BufferLen, ( LPBYTE )Buffer ) )
  272. {
  273. NlPrint((NL_CRITICAL, "CryptGenRandom failed with %lu\n", GetLastError() ));
  274. return FALSE;
  275. }
  276. return TRUE;
  277. }
  278. NET_API_STATUS
  279. NET_API_FUNCTION
  280. NetpValidateMachineAccount(
  281. IN LPWSTR lpDc,
  282. IN LPWSTR lpDomain,
  283. IN LPWSTR lpMachine,
  284. IN LPWSTR lpPassword
  285. )
  286. /*++
  287. Routine Description:
  288. Performs validation that the machine account exists and has the same password we expect
  289. The internals of this function were lifted completely from SimulateFullSync() in
  290. ..\svcdlls\logonsrv\server\nltest.c,
  291. Arguments:
  292. lpDc -- Name of the Dc
  293. lpDomain -- Name of the domain
  294. lpMachine -- Current machine
  295. lpPassword -- Password that should be on the account.
  296. Returns:
  297. NERR_Success -- Success
  298. --*/
  299. {
  300. NTSTATUS Status = STATUS_SUCCESS;
  301. NETLOGON_CREDENTIAL ServerChallenge;
  302. NETLOGON_CREDENTIAL ClientChallenge;
  303. NETLOGON_CREDENTIAL ComputedServerCredential;
  304. NETLOGON_CREDENTIAL ReturnedServerCredential;
  305. NETLOGON_CREDENTIAL AuthenticationSeed;
  306. NETLOGON_SESSION_KEY SessionKey;
  307. WCHAR AccountName[SSI_ACCOUNT_NAME_LENGTH+1];
  308. UNICODE_STRING Password;
  309. NT_OWF_PASSWORD NtOwfPassword;
  310. UNREFERENCED_PARAMETER( lpDomain );
  311. ASSERT( lpPassword );
  312. //
  313. // initialize Crypto Provider.
  314. // (required for NlComputeChallenge).
  315. //
  316. if ( !CryptAcquireContext(
  317. &NlGlobalCryptProvider,
  318. NULL,
  319. NULL,
  320. PROV_RSA_FULL,
  321. CRYPT_VERIFYCONTEXT
  322. ))
  323. {
  324. NlGlobalCryptProvider = (HCRYPTPROV)NULL;
  325. return (NET_API_STATUS)GetLastError();
  326. }
  327. //
  328. // Prepare our challenge
  329. //
  330. NlComputeChallenge( &ClientChallenge );
  331. //
  332. // free cryptographic service provider.
  333. //
  334. if ( NlGlobalCryptProvider ) {
  335. CryptReleaseContext( NlGlobalCryptProvider, 0 );
  336. NlGlobalCryptProvider = (HCRYPTPROV)NULL;
  337. }
  338. //
  339. // Get the primary's challenge
  340. //
  341. Status = I_NetServerReqChallenge(lpDc,
  342. lpMachine,
  343. &ClientChallenge,
  344. &ServerChallenge );
  345. if ( !NT_SUCCESS( Status ) ) {
  346. goto ValidateMachineAccountError;
  347. }
  348. Password.Length = Password.MaximumLength = wcslen(lpPassword) * sizeof(WCHAR);
  349. Password.Buffer = lpPassword;
  350. //
  351. // Compute the NT OWF password for this user.
  352. //
  353. Status = RtlCalculateNtOwfPassword( &Password, &NtOwfPassword );
  354. if ( !NT_SUCCESS( Status ) ) {
  355. goto ValidateMachineAccountError;
  356. }
  357. //
  358. // Actually compute the session key given the two challenges and the
  359. // password.
  360. //
  361. NlMakeSessionKey(
  362. #if(_WIN32_WINNT >= 0x0500)
  363. 0,
  364. #endif
  365. &NtOwfPassword,
  366. &ClientChallenge,
  367. &ServerChallenge,
  368. &SessionKey );
  369. //
  370. // Prepare credentials using our challenge.
  371. //
  372. NlComputeCredentials( &ClientChallenge,
  373. &AuthenticationSeed,
  374. &SessionKey );
  375. //
  376. // Send these credentials to primary. The primary will compute
  377. // credentials using the challenge supplied by us and compare
  378. // with these. If both match then it will compute credentials
  379. // using its challenge and return it to us for verification
  380. //
  381. wcscpy( AccountName, lpMachine );
  382. wcscat( AccountName, SSI_ACCOUNT_NAME_POSTFIX);
  383. Status = I_NetServerAuthenticate( lpDc,
  384. AccountName,
  385. WorkstationSecureChannel,
  386. lpMachine,
  387. &AuthenticationSeed,
  388. &ReturnedServerCredential );
  389. if ( !NT_SUCCESS( Status ) ) {
  390. goto ValidateMachineAccountError;
  391. }
  392. //
  393. // The DC returned a server credential to us,
  394. // ensure the server credential matches the one we would compute.
  395. //
  396. NlComputeCredentials( &ServerChallenge,
  397. &ComputedServerCredential,
  398. &SessionKey);
  399. if (RtlCompareMemory( &ReturnedServerCredential,
  400. &ComputedServerCredential,
  401. sizeof(ReturnedServerCredential)) !=
  402. sizeof(ReturnedServerCredential)) {
  403. Status = STATUS_ACCESS_DENIED;
  404. }
  405. ValidateMachineAccountError:
  406. if ( Status == STATUS_ACCESS_DENIED ) {
  407. Status = STATUS_LOGON_FAILURE;
  408. }
  409. if ( !NT_SUCCESS( Status ) ) {
  410. NetpLog(( "Failed to validate machine account for %ws against %ws: 0x%lx\n",
  411. lpMachine, lpDc, Status ));
  412. }
  413. return( RtlNtStatusToDosError( Status ) );
  414. }