SSPIHelper.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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_ACCESS_TOKEN = 18;
  42. [StructLayout(LayoutKind.Sequential)]
  43. private struct SECURITY_INTEGER
  44. {
  45. public uint LowPart;
  46. public int HighPart;
  47. };
  48. /// <summary>
  49. /// When using the NTLM package, the maximum character lengths for user name, password, and domain are 256, 256, and 15, respectively.
  50. /// </summary>
  51. [StructLayout(LayoutKind.Sequential)]
  52. private struct SEC_WINNT_AUTH_IDENTITY
  53. {
  54. public string User;
  55. public uint UserLength;
  56. public string Domain;
  57. public uint DomainLength;
  58. public string Password;
  59. public uint PasswordLength;
  60. public uint Flags;
  61. };
  62. [DllImport("secur32.dll", SetLastError = true)]
  63. private static extern uint AcquireCredentialsHandle(
  64. string pszPrincipal,
  65. string pszPackage,
  66. uint fCredentialUse,
  67. IntPtr pvLogonID,
  68. IntPtr pAuthData,
  69. IntPtr pGetKeyFn,
  70. IntPtr pvGetKeyArgument,
  71. out SecHandle phCredential,
  72. out SECURITY_INTEGER ptsExpiry);
  73. [DllImport("secur32.dll", SetLastError = true)]
  74. private static extern uint InitializeSecurityContext(
  75. ref SecHandle phCredential,
  76. IntPtr phContext,
  77. string pszTargetName,
  78. uint fContextReq,
  79. uint Reserved1,
  80. uint TargetDataRep,
  81. IntPtr pInput,
  82. uint Reserved2,
  83. ref SecHandle phNewContext,
  84. ref SecBufferDesc pOutput,
  85. out uint pfContextAttr,
  86. out SECURITY_INTEGER ptsExpiry);
  87. [DllImport("secur32.dll", SetLastError = true)]
  88. private static extern uint InitializeSecurityContext(
  89. IntPtr phCredential,
  90. ref SecHandle phContext,
  91. string pszTargetName,
  92. uint fContextReq,
  93. uint Reserved1,
  94. uint TargetDataRep,
  95. ref SecBufferDesc pInput,
  96. uint Reserved2,
  97. ref SecHandle phNewContext,
  98. ref SecBufferDesc pOutput,
  99. out uint pfContextAttr,
  100. out SECURITY_INTEGER ptsExpiry);
  101. [DllImport("secur32.dll", SetLastError = true)]
  102. private static extern uint AcceptSecurityContext(
  103. ref SecHandle phCredential,
  104. IntPtr phContext,
  105. ref SecBufferDesc pInput,
  106. uint fContextReq,
  107. uint TargetDataRep,
  108. ref SecHandle phNewContext,
  109. ref SecBufferDesc pOutput,
  110. out uint pfContextAttr,
  111. out SECURITY_INTEGER ptsTimeStamp);
  112. [DllImport("secur32.dll", SetLastError = true)]
  113. private static extern uint AcceptSecurityContext(
  114. IntPtr phCredential,
  115. ref SecHandle phContext,
  116. ref SecBufferDesc pInput,
  117. uint fContextReq,
  118. uint TargetDataRep,
  119. ref SecHandle phNewContext,
  120. ref SecBufferDesc pOutput,
  121. out uint pfContextAttr,
  122. out SECURITY_INTEGER ptsTimeStamp);
  123. [DllImport("secur32.Dll", SetLastError = true)]
  124. private static extern uint QueryContextAttributes(
  125. ref SecHandle phContext,
  126. uint ulAttribute,
  127. out IntPtr pBuffer);
  128. [DllImport("Secur32.dll")]
  129. private extern static uint FreeContextBuffer(
  130. IntPtr pvContextBuffer
  131. );
  132. [DllImport("Secur32.dll")]
  133. private extern static uint FreeCredentialsHandle(
  134. ref SecHandle phCredential
  135. );
  136. [DllImport("Secur32.dll")]
  137. public extern static uint DeleteSecurityContext(
  138. ref SecHandle phContext
  139. );
  140. public static SecHandle AcquireNTLMCredentialsHandle()
  141. {
  142. return AcquireNTLMCredentialsHandle(null);
  143. }
  144. public static SecHandle AcquireNTLMCredentialsHandle(string domainName, string userName, string password)
  145. {
  146. SEC_WINNT_AUTH_IDENTITY auth = new SEC_WINNT_AUTH_IDENTITY();
  147. auth.Domain = domainName;
  148. auth.DomainLength = (uint)domainName.Length;
  149. auth.User = userName;
  150. auth.UserLength = (uint)userName.Length;
  151. auth.Password = password;
  152. auth.PasswordLength = (uint)password.Length;
  153. auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
  154. return AcquireNTLMCredentialsHandle(auth);
  155. }
  156. private static SecHandle AcquireNTLMCredentialsHandle(SEC_WINNT_AUTH_IDENTITY? auth)
  157. {
  158. SecHandle credential;
  159. SECURITY_INTEGER expiry;
  160. IntPtr pAuthData;
  161. if (auth.HasValue)
  162. {
  163. pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value));
  164. Marshal.StructureToPtr(auth.Value, pAuthData, false);
  165. }
  166. else
  167. {
  168. pAuthData = IntPtr.Zero;
  169. }
  170. uint result = AcquireCredentialsHandle(null, "NTLM", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry);
  171. if (pAuthData != IntPtr.Zero)
  172. {
  173. Marshal.FreeHGlobal(pAuthData);
  174. }
  175. if (result != SEC_E_OK)
  176. {
  177. throw new Exception("AcquireCredentialsHandle failed, Error code 0x" + result.ToString("X"));
  178. }
  179. return credential;
  180. }
  181. public static byte[] GetType1Message(string userName, string password, out SecHandle clientContext)
  182. {
  183. return GetType1Message(String.Empty, userName, password, out clientContext);
  184. }
  185. public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext)
  186. {
  187. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password);
  188. clientContext = new SecHandle();
  189. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  190. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  191. uint contextAttributes;
  192. SECURITY_INTEGER expiry;
  193. 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);
  194. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  195. {
  196. if (result == SEC_E_INVALID_HANDLE)
  197. {
  198. throw new Exception("InitializeSecurityContext failed, Invalid handle");
  199. }
  200. else if (result == SEC_E_BUFFER_TOO_SMALL)
  201. {
  202. throw new Exception("InitializeSecurityContext failed, Buffer too small");
  203. }
  204. else
  205. {
  206. throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X"));
  207. }
  208. }
  209. FreeCredentialsHandle(ref credentialsHandle);
  210. byte[] messageBytes = output.GetBufferBytes(0);
  211. outputBuffer.Dispose();
  212. output.Dispose();
  213. return messageBytes;
  214. }
  215. public static byte[] GetType3Message(SecHandle clientContext, byte[] type2Message)
  216. {
  217. SecHandle newContext = new SecHandle();
  218. SecBuffer inputBuffer = new SecBuffer(type2Message);
  219. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  220. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  221. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  222. uint contextAttributes;
  223. SECURITY_INTEGER expiry;
  224. 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);
  225. if (result != SEC_E_OK)
  226. {
  227. if (result == SEC_E_INVALID_HANDLE)
  228. {
  229. throw new Exception("InitializeSecurityContext failed, invalid handle");
  230. }
  231. else if (result == SEC_E_INVALID_TOKEN)
  232. {
  233. throw new Exception("InitializeSecurityContext failed, Invalid token");
  234. }
  235. else if (result == SEC_E_BUFFER_TOO_SMALL)
  236. {
  237. throw new Exception("InitializeSecurityContext failed, buffer too small");
  238. }
  239. else
  240. {
  241. throw new Exception("InitializeSecurityContext failed, error code 0x" + result.ToString("X"));
  242. }
  243. }
  244. byte[] messageBytes = output.GetBufferBytes(0);
  245. inputBuffer.Dispose();
  246. input.Dispose();
  247. outputBuffer.Dispose();
  248. output.Dispose();
  249. return messageBytes;
  250. }
  251. public static byte[] GetType2Message(byte[] type1MessageBytes, out SecHandle serverContext)
  252. {
  253. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle();
  254. SecBuffer inputBuffer = new SecBuffer(type1MessageBytes);
  255. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  256. serverContext = new SecHandle();
  257. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  258. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  259. uint contextAttributes;
  260. SECURITY_INTEGER timestamp;
  261. 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);
  262. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  263. {
  264. if (result == SEC_E_INVALID_HANDLE)
  265. {
  266. throw new Exception("AcceptSecurityContext failed, invalid handle");
  267. }
  268. else if (result == SEC_E_INVALID_TOKEN)
  269. {
  270. throw new Exception("InitializeSecurityContext failed, Invalid token");
  271. }
  272. else if (result == SEC_E_BUFFER_TOO_SMALL)
  273. {
  274. throw new Exception("AcceptSecurityContext failed, buffer too small");
  275. }
  276. else
  277. {
  278. throw new Exception("AcceptSecurityContext failed, error code 0x" + result.ToString("X"));
  279. }
  280. }
  281. FreeCredentialsHandle(ref credentialsHandle);
  282. byte[] messageBytes = output.GetBufferBytes(0);
  283. inputBuffer.Dispose();
  284. input.Dispose();
  285. outputBuffer.Dispose();
  286. output.Dispose();
  287. return messageBytes;
  288. }
  289. /// <summary>
  290. /// AcceptSecurityContext will return SEC_E_LOGON_DENIED when the password is correct in these cases:
  291. /// 1. The account is listed under the "Deny access to this computer from the network" list.
  292. /// 2. 'limitblankpassworduse' is set to 1, non-guest is attempting to login with an empty password,
  293. /// and the Guest account is disabled, has non-empty pasword set or listed under the "Deny access to this computer from the network" list.
  294. /// </summary>
  295. /// <remarks>
  296. /// 1. 'limitblankpassworduse' will not affect the Guest account.
  297. /// 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.
  298. /// - 'limitblankpassworduse' is set to 1.
  299. /// - The user has an empty password set.
  300. /// - Guest is NOT listed in the "Deny access to this computer from the network" list.
  301. /// - Guest is enabled and has empty pasword set.
  302. /// </remarks>
  303. public static bool AuthenticateType3Message(SecHandle serverContext, byte[] type3MessageBytes)
  304. {
  305. SecHandle newContext = new SecHandle();
  306. SecBuffer inputBuffer = new SecBuffer(type3MessageBytes);
  307. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  308. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  309. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  310. uint contextAttributes;
  311. SECURITY_INTEGER timestamp;
  312. 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);
  313. inputBuffer.Dispose();
  314. input.Dispose();
  315. outputBuffer.Dispose();
  316. output.Dispose();
  317. if (result == SEC_E_OK)
  318. {
  319. return true;
  320. }
  321. else if ((uint)result == SEC_E_LOGON_DENIED)
  322. {
  323. return false;
  324. }
  325. else
  326. {
  327. if (result == SEC_E_INVALID_HANDLE)
  328. {
  329. throw new Exception("AcceptSecurityContext failed, invalid handle");
  330. }
  331. else if (result == SEC_E_INVALID_TOKEN)
  332. {
  333. throw new Exception("AcceptSecurityContext failed, invalid security token");
  334. }
  335. else
  336. {
  337. throw new Exception("AcceptSecurityContext failed, error code 0x" + result.ToString("X"));
  338. }
  339. }
  340. }
  341. public static IntPtr GetAccessToken(SecHandle serverContext)
  342. {
  343. IntPtr pBuffer;
  344. uint result = QueryContextAttributes(ref serverContext, SECPKG_ATTR_ACCESS_TOKEN, out pBuffer);
  345. if (result == SEC_E_OK)
  346. {
  347. return pBuffer;
  348. }
  349. else
  350. {
  351. return IntPtr.Zero;
  352. }
  353. }
  354. }
  355. }