SSPIHelper.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Runtime.InteropServices;
  10. namespace SMBLibrary.Win32.Security
  11. {
  12. [StructLayout(LayoutKind.Sequential)]
  13. public struct SecHandle
  14. {
  15. public IntPtr dwLower;
  16. public IntPtr dwUpper;
  17. };
  18. public class SSPIHelper
  19. {
  20. private const int MAX_TOKEN_SIZE = 12000;
  21. private const uint SEC_E_OK = 0;
  22. private const uint SEC_I_CONTINUE_NEEDED = 0x00090312;
  23. private const uint SEC_E_INVALID_HANDLE = 0x80090301;
  24. private const uint SEC_E_INVALID_TOKEN = 0x80090308;
  25. private const uint SEC_E_LOGON_DENIED = 0x8009030C;
  26. private const uint SEC_E_BUFFER_TOO_SMALL = 0x80090321;
  27. private const uint SECURITY_NETWORK_DREP = 0x00;
  28. private const uint SECURITY_NATIVE_DREP = 0x10;
  29. private const uint SECPKG_CRED_INBOUND = 0x01;
  30. private const uint SECPKG_CRED_OUTBOUND = 0x02;
  31. private const uint SECPKG_CRED_BOTH = 0x03;
  32. private const uint ISC_REQ_CONFIDENTIALITY = 0x00000010;
  33. private const uint ISC_REQ_ALLOCATE_MEMORY = 0x00000100;
  34. private const uint ISC_REQ_INTEGRITY = 0x00010000;
  35. private const uint ASC_REQ_REPLAY_DETECT = 0x00000004;
  36. private const uint ASC_REQ_CONFIDENTIALITY = 0x00000010;
  37. private const uint ASC_REQ_USE_SESSION_KEY = 0x00000020;
  38. private const uint ASC_REQ_INTEGRITY = 0x00020000;
  39. private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 1;
  40. private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 2;
  41. private const uint SECPKG_ATTR_NAME = 1; // Username
  42. private const uint SECPKG_ATTR_SESSION_KEY = 9;
  43. private const uint SECPKG_ATTR_ACCESS_TOKEN = 18;
  44. [StructLayout(LayoutKind.Sequential)]
  45. private struct SECURITY_INTEGER
  46. {
  47. public uint LowPart;
  48. public int HighPart;
  49. };
  50. /// <summary>
  51. /// When using the NTLM package, the maximum character lengths for user name, password, and domain are 256, 256, and 15, respectively.
  52. /// </summary>
  53. [StructLayout(LayoutKind.Sequential)]
  54. private struct SEC_WINNT_AUTH_IDENTITY
  55. {
  56. public string User;
  57. public uint UserLength;
  58. public string Domain;
  59. public uint DomainLength;
  60. public string Password;
  61. public uint PasswordLength;
  62. public uint Flags;
  63. };
  64. [StructLayout(LayoutKind.Sequential)]
  65. public struct SecPkgContext_SessionKey
  66. {
  67. public uint SessionKeyLength;
  68. public IntPtr SessionKey;
  69. }
  70. [DllImport("secur32.dll", SetLastError = true)]
  71. private static extern uint AcquireCredentialsHandle(
  72. string pszPrincipal,
  73. string pszPackage,
  74. uint fCredentialUse,
  75. IntPtr pvLogonID,
  76. IntPtr pAuthData,
  77. IntPtr pGetKeyFn,
  78. IntPtr pvGetKeyArgument,
  79. out SecHandle phCredential,
  80. out SECURITY_INTEGER ptsExpiry);
  81. [DllImport("secur32.dll", SetLastError = true)]
  82. private static extern uint InitializeSecurityContext(
  83. ref SecHandle phCredential,
  84. IntPtr phContext,
  85. string pszTargetName,
  86. uint fContextReq,
  87. uint Reserved1,
  88. uint TargetDataRep,
  89. IntPtr pInput,
  90. uint Reserved2,
  91. ref SecHandle phNewContext,
  92. ref SecBufferDesc pOutput,
  93. out uint pfContextAttr,
  94. out SECURITY_INTEGER ptsExpiry);
  95. [DllImport("secur32.dll", SetLastError = true)]
  96. private static extern uint InitializeSecurityContext(
  97. IntPtr phCredential,
  98. ref SecHandle phContext,
  99. string pszTargetName,
  100. uint fContextReq,
  101. uint Reserved1,
  102. uint TargetDataRep,
  103. ref SecBufferDesc pInput,
  104. uint Reserved2,
  105. ref SecHandle phNewContext,
  106. ref SecBufferDesc pOutput,
  107. out uint pfContextAttr,
  108. out SECURITY_INTEGER ptsExpiry);
  109. [DllImport("secur32.dll", SetLastError = true)]
  110. private static extern uint AcceptSecurityContext(
  111. ref SecHandle phCredential,
  112. IntPtr phContext,
  113. ref SecBufferDesc pInput,
  114. uint fContextReq,
  115. uint TargetDataRep,
  116. ref SecHandle phNewContext,
  117. ref SecBufferDesc pOutput,
  118. out uint pfContextAttr,
  119. out SECURITY_INTEGER ptsTimeStamp);
  120. [DllImport("secur32.dll", SetLastError = true)]
  121. private static extern uint AcceptSecurityContext(
  122. IntPtr phCredential,
  123. ref SecHandle phContext,
  124. ref SecBufferDesc pInput,
  125. uint fContextReq,
  126. uint TargetDataRep,
  127. ref SecHandle phNewContext,
  128. ref SecBufferDesc pOutput,
  129. out uint pfContextAttr,
  130. out SECURITY_INTEGER ptsTimeStamp);
  131. [DllImport("secur32.Dll", SetLastError = true)]
  132. private static extern uint QueryContextAttributes(
  133. ref SecHandle phContext,
  134. uint ulAttribute,
  135. out string value);
  136. [DllImport("secur32.Dll", SetLastError = true)]
  137. private static extern uint QueryContextAttributes(
  138. ref SecHandle phContext,
  139. uint ulAttribute,
  140. out IntPtr value);
  141. [DllImport("secur32.Dll", SetLastError = true)]
  142. private static extern uint QueryContextAttributes(
  143. ref SecHandle phContext,
  144. uint ulAttribute,
  145. out SecPkgContext_SessionKey value);
  146. [DllImport("Secur32.dll")]
  147. private extern static uint FreeContextBuffer(
  148. IntPtr pvContextBuffer
  149. );
  150. [DllImport("Secur32.dll")]
  151. private extern static uint FreeCredentialsHandle(
  152. ref SecHandle phCredential
  153. );
  154. [DllImport("Secur32.dll")]
  155. public extern static uint DeleteSecurityContext(
  156. ref SecHandle phContext
  157. );
  158. public static SecHandle AcquireNTLMCredentialsHandle()
  159. {
  160. return AcquireNTLMCredentialsHandle(null);
  161. }
  162. public static SecHandle AcquireNTLMCredentialsHandle(string domainName, string userName, string password)
  163. {
  164. SEC_WINNT_AUTH_IDENTITY auth = new SEC_WINNT_AUTH_IDENTITY();
  165. auth.Domain = domainName;
  166. auth.DomainLength = (uint)domainName.Length;
  167. auth.User = userName;
  168. auth.UserLength = (uint)userName.Length;
  169. auth.Password = password;
  170. auth.PasswordLength = (uint)password.Length;
  171. auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
  172. return AcquireNTLMCredentialsHandle(auth);
  173. }
  174. private static SecHandle AcquireNTLMCredentialsHandle(SEC_WINNT_AUTH_IDENTITY? auth)
  175. {
  176. SecHandle credential;
  177. SECURITY_INTEGER expiry;
  178. IntPtr pAuthData;
  179. if (auth.HasValue)
  180. {
  181. pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value));
  182. Marshal.StructureToPtr(auth.Value, pAuthData, false);
  183. }
  184. else
  185. {
  186. pAuthData = IntPtr.Zero;
  187. }
  188. uint result = AcquireCredentialsHandle(null, "NTLM", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry);
  189. if (pAuthData != IntPtr.Zero)
  190. {
  191. Marshal.FreeHGlobal(pAuthData);
  192. }
  193. if (result != SEC_E_OK)
  194. {
  195. throw new Exception("AcquireCredentialsHandle failed, Error code 0x" + result.ToString("X8"));
  196. }
  197. return credential;
  198. }
  199. public static byte[] GetType1Message(string userName, string password, out SecHandle clientContext)
  200. {
  201. return GetType1Message(String.Empty, userName, password, out clientContext);
  202. }
  203. public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext)
  204. {
  205. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password);
  206. clientContext = new SecHandle();
  207. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  208. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  209. uint contextAttributes;
  210. SECURITY_INTEGER expiry;
  211. uint result = InitializeSecurityContext(ref credentialsHandle, IntPtr.Zero, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, IntPtr.Zero, 0, ref clientContext, ref output, out contextAttributes, out expiry);
  212. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  213. {
  214. if (result == SEC_E_INVALID_HANDLE)
  215. {
  216. throw new Exception("InitializeSecurityContext failed, Invalid handle");
  217. }
  218. else if (result == SEC_E_BUFFER_TOO_SMALL)
  219. {
  220. throw new Exception("InitializeSecurityContext failed, Buffer too small");
  221. }
  222. else
  223. {
  224. throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8"));
  225. }
  226. }
  227. FreeCredentialsHandle(ref credentialsHandle);
  228. byte[] messageBytes = output.GetBufferBytes(0);
  229. outputBuffer.Dispose();
  230. output.Dispose();
  231. return messageBytes;
  232. }
  233. public static byte[] GetType3Message(SecHandle clientContext, byte[] type2Message)
  234. {
  235. SecHandle newContext = new SecHandle();
  236. SecBuffer inputBuffer = new SecBuffer(type2Message);
  237. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  238. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  239. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  240. uint contextAttributes;
  241. SECURITY_INTEGER expiry;
  242. uint result = InitializeSecurityContext(IntPtr.Zero, ref clientContext, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, ref input, 0, ref newContext, ref output, out contextAttributes, out expiry);
  243. if (result != SEC_E_OK)
  244. {
  245. if (result == SEC_E_INVALID_HANDLE)
  246. {
  247. throw new Exception("InitializeSecurityContext failed, Invalid handle");
  248. }
  249. else if (result == SEC_E_INVALID_TOKEN)
  250. {
  251. throw new Exception("InitializeSecurityContext failed, Invalid token");
  252. }
  253. else if (result == SEC_E_BUFFER_TOO_SMALL)
  254. {
  255. throw new Exception("InitializeSecurityContext failed, Buffer too small");
  256. }
  257. else
  258. {
  259. throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8"));
  260. }
  261. }
  262. byte[] messageBytes = output.GetBufferBytes(0);
  263. inputBuffer.Dispose();
  264. input.Dispose();
  265. outputBuffer.Dispose();
  266. output.Dispose();
  267. return messageBytes;
  268. }
  269. public static byte[] GetType2Message(byte[] type1MessageBytes, out SecHandle serverContext)
  270. {
  271. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle();
  272. SecBuffer inputBuffer = new SecBuffer(type1MessageBytes);
  273. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  274. serverContext = new SecHandle();
  275. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  276. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  277. uint contextAttributes;
  278. SECURITY_INTEGER timestamp;
  279. uint result = AcceptSecurityContext(ref credentialsHandle, IntPtr.Zero, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref serverContext, ref output, out contextAttributes, out timestamp);
  280. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  281. {
  282. if (result == SEC_E_INVALID_HANDLE)
  283. {
  284. throw new Exception("AcceptSecurityContext failed, Invalid handle");
  285. }
  286. else if (result == SEC_E_INVALID_TOKEN)
  287. {
  288. throw new Exception("AcceptSecurityContext failed, Invalid token");
  289. }
  290. else if (result == SEC_E_BUFFER_TOO_SMALL)
  291. {
  292. throw new Exception("AcceptSecurityContext failed, Buffer too small");
  293. }
  294. else
  295. {
  296. throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8"));
  297. }
  298. }
  299. FreeCredentialsHandle(ref credentialsHandle);
  300. byte[] messageBytes = output.GetBufferBytes(0);
  301. inputBuffer.Dispose();
  302. input.Dispose();
  303. outputBuffer.Dispose();
  304. output.Dispose();
  305. return messageBytes;
  306. }
  307. /// <summary>
  308. /// AcceptSecurityContext will return SEC_E_LOGON_DENIED when the password is correct in these cases:
  309. /// 1. The account is listed under the "Deny access to this computer from the network" list.
  310. /// 2. 'limitblankpassworduse' is set to 1, non-guest is attempting to login with an empty password,
  311. /// and the Guest account is disabled, has non-empty pasword set or listed under the "Deny access to this computer from the network" list.
  312. ///
  313. /// Note: "If the Guest account is enabled, SSPI logon may succeed as Guest for user credentials that are not valid".
  314. /// </summary>
  315. /// <remarks>
  316. /// 1. 'limitblankpassworduse' will not affect the Guest account.
  317. /// 2. Listing the user in the "Deny access to this computer from the network" or the "Deny logon locally" lists will not affect AcceptSecurityContext if all of these conditions are met.
  318. /// - 'limitblankpassworduse' is set to 1.
  319. /// - The user has an empty password set.
  320. /// - Guest is NOT listed in the "Deny access to this computer from the network" list.
  321. /// - Guest is enabled and has empty pasword set.
  322. /// </remarks>
  323. public static bool AuthenticateType3Message(SecHandle serverContext, byte[] type3MessageBytes)
  324. {
  325. SecHandle newContext = new SecHandle();
  326. SecBuffer inputBuffer = new SecBuffer(type3MessageBytes);
  327. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  328. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  329. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  330. uint contextAttributes;
  331. SECURITY_INTEGER timestamp;
  332. uint result = AcceptSecurityContext(IntPtr.Zero, ref serverContext, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref newContext, ref output, out contextAttributes, out timestamp);
  333. inputBuffer.Dispose();
  334. input.Dispose();
  335. outputBuffer.Dispose();
  336. output.Dispose();
  337. if (result == SEC_E_OK)
  338. {
  339. return true;
  340. }
  341. else if ((uint)result == SEC_E_LOGON_DENIED)
  342. {
  343. return false;
  344. }
  345. else
  346. {
  347. if (result == SEC_E_INVALID_HANDLE)
  348. {
  349. throw new Exception("AcceptSecurityContext failed, Invalid handle");
  350. }
  351. else if (result == SEC_E_INVALID_TOKEN)
  352. {
  353. throw new Exception("AcceptSecurityContext failed, Invalid security token");
  354. }
  355. else
  356. {
  357. throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8"));
  358. }
  359. }
  360. }
  361. public static string GetUserName(SecHandle context)
  362. {
  363. string userName;
  364. uint result = QueryContextAttributes(ref context, SECPKG_ATTR_NAME, out userName);
  365. if (result == SEC_E_OK)
  366. {
  367. return userName;
  368. }
  369. else
  370. {
  371. return null;
  372. }
  373. }
  374. /// <summary>
  375. /// Windows Vista or newer is required for SECPKG_ATTR_SESSION_KEY to work.
  376. /// Windows XP / Server 2003 will return SEC_E_INVALID_TOKEN.
  377. /// </summary>
  378. public static byte[] GetSessionKey(SecHandle context)
  379. {
  380. SecPkgContext_SessionKey sessionKey;
  381. uint result = QueryContextAttributes(ref context, SECPKG_ATTR_SESSION_KEY, out sessionKey);
  382. if (result == SEC_E_OK)
  383. {
  384. int length = (int)sessionKey.SessionKeyLength;
  385. byte[] sessionKeyBytes = new byte[length];
  386. Marshal.Copy(sessionKey.SessionKey, sessionKeyBytes, 0, length);
  387. return sessionKeyBytes;
  388. }
  389. else
  390. {
  391. return null;
  392. }
  393. }
  394. public static IntPtr GetAccessToken(SecHandle serverContext)
  395. {
  396. IntPtr accessToken;
  397. uint result = QueryContextAttributes(ref serverContext, SECPKG_ATTR_ACCESS_TOKEN, out accessToken);
  398. if (result == SEC_E_OK)
  399. {
  400. return accessToken;
  401. }
  402. else
  403. {
  404. return IntPtr.Zero;
  405. }
  406. }
  407. }
  408. }