SSPIHelper.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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. using System.Text;
  11. namespace SMBLibrary.Authentication.Win32
  12. {
  13. [StructLayout(LayoutKind.Sequential)]
  14. public struct SecHandle
  15. {
  16. public IntPtr dwLower;
  17. public IntPtr dwUpper;
  18. };
  19. public class SSPIHelper
  20. {
  21. private const int MAX_TOKEN_SIZE = 12000;
  22. private const uint SEC_E_OK = 0;
  23. private const uint SEC_I_CONTINUE_NEEDED = 0x90312;
  24. private const uint SEC_E_INVALID_HANDLE = 0x80090301;
  25. private const uint SEC_E_INVALID_TOKEN = 0x80090308;
  26. private const uint SEC_E_LOGON_DENIED = 0x8009030C;
  27. private const uint SEC_E_BUFFER_TOO_SMALL = 0x80090321;
  28. private const int SECURITY_NETWORK_DREP = 0x00;
  29. private const int SECURITY_NATIVE_DREP = 0x10;
  30. private const int SECPKG_CRED_INBOUND = 0x01;
  31. private const int SECPKG_CRED_OUTBOUND = 0x02;
  32. private const int SECPKG_CRED_BOTH = 0x03;
  33. private const int ISC_REQ_CONFIDENTIALITY = 0x00000010;
  34. private const int ISC_REQ_ALLOCATE_MEMORY = 0x00000100;
  35. private const int ISC_REQ_INTEGRITY = 0x00010000;
  36. private const int ASC_REQ_REPLAY_DETECT = 0x00000004;
  37. private const int ASC_REQ_CONFIDENTIALITY = 0x00000010;
  38. private const int ASC_REQ_USE_SESSION_KEY = 0x00000020;
  39. private const int ASC_REQ_INTEGRITY = 0x00020000;
  40. private const int SEC_WINNT_AUTH_IDENTITY_ANSI = 1;
  41. private const int SEC_WINNT_AUTH_IDENTITY_UNICODE = 2;
  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 int UserLength;
  56. public string Domain;
  57. public int DomainLength;
  58. public string Password;
  59. public int PasswordLength;
  60. public int Flags;
  61. };
  62. [DllImport("secur32.dll", SetLastError = true)]
  63. private static extern int AcquireCredentialsHandle(
  64. string pszPrincipal,
  65. string pszPackage,
  66. int 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 int InitializeSecurityContext(
  75. ref SecHandle phCredential,
  76. IntPtr phContext,
  77. string pszTargetName,
  78. int fContextReq,
  79. int Reserved1,
  80. int TargetDataRep,
  81. IntPtr pInput,
  82. int 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 int InitializeSecurityContext(
  89. IntPtr phCredential,
  90. ref SecHandle phContext,
  91. string pszTargetName,
  92. int fContextReq,
  93. int Reserved1,
  94. int TargetDataRep,
  95. ref SecBufferDesc pInput,
  96. int 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 int 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 int 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")]
  124. private extern static int FreeContextBuffer(
  125. IntPtr pvContextBuffer
  126. );
  127. [DllImport("Secur32.dll")]
  128. private extern static int FreeCredentialsHandle(
  129. ref SecHandle phCredential
  130. );
  131. [DllImport("Secur32.dll")]
  132. public extern static int DeleteSecurityContext(
  133. ref SecHandle phContext
  134. );
  135. public static SecHandle AcquireNTLMCredentialsHandle()
  136. {
  137. return AcquireNTLMCredentialsHandle(null);
  138. }
  139. public static SecHandle AcquireNTLMCredentialsHandle(string domainName, string userName, string password)
  140. {
  141. SEC_WINNT_AUTH_IDENTITY auth = new SEC_WINNT_AUTH_IDENTITY();
  142. auth.Domain = domainName;
  143. auth.DomainLength = domainName.Length;
  144. auth.User = userName;
  145. auth.UserLength = userName.Length;
  146. auth.Password = password;
  147. auth.PasswordLength = password.Length;
  148. auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
  149. return AcquireNTLMCredentialsHandle(auth);
  150. }
  151. private static SecHandle AcquireNTLMCredentialsHandle(SEC_WINNT_AUTH_IDENTITY? auth)
  152. {
  153. SecHandle credential;
  154. SECURITY_INTEGER expiry;
  155. IntPtr pAuthData;
  156. if (auth.HasValue)
  157. {
  158. pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value));
  159. Marshal.StructureToPtr(auth.Value, pAuthData, false);
  160. }
  161. else
  162. {
  163. pAuthData = IntPtr.Zero;
  164. }
  165. int result = AcquireCredentialsHandle(null, "NTLM", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry);
  166. if (pAuthData != IntPtr.Zero)
  167. {
  168. Marshal.FreeHGlobal(pAuthData);
  169. }
  170. if (result != SEC_E_OK)
  171. {
  172. throw new Exception("AcquireCredentialsHandle failed, Error code " + ((uint)result).ToString("X"));
  173. }
  174. return credential;
  175. }
  176. public static byte[] GetType1Message(string userName, string password, out SecHandle clientContext)
  177. {
  178. return GetType1Message(String.Empty, userName, password, out clientContext);
  179. }
  180. public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext)
  181. {
  182. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password);
  183. clientContext = new SecHandle();
  184. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  185. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  186. uint contextAttributes;
  187. SECURITY_INTEGER expiry;
  188. int 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);
  189. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  190. {
  191. if ((uint)result == SEC_E_INVALID_HANDLE)
  192. {
  193. throw new Exception("InitializeSecurityContext failed, Invalid handle");
  194. }
  195. else if ((uint)result == SEC_E_BUFFER_TOO_SMALL)
  196. {
  197. throw new Exception("InitializeSecurityContext failed, Buffer too small");
  198. }
  199. else
  200. {
  201. throw new Exception("InitializeSecurityContext failed, Error code " + ((uint)result).ToString("X"));
  202. }
  203. }
  204. FreeCredentialsHandle(ref credentialsHandle);
  205. byte[] messageBytes = outputBuffer.GetBufferBytes();
  206. outputBuffer.Dispose();
  207. output.Dispose();
  208. return messageBytes;
  209. }
  210. public static byte[] GetType3Message(SecHandle clientContext, byte[] type2Message)
  211. {
  212. SecHandle newContext = new SecHandle();
  213. SecBuffer inputBuffer = new SecBuffer(type2Message);
  214. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  215. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  216. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  217. uint contextAttributes;
  218. SECURITY_INTEGER expiry;
  219. int 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);
  220. if (result != SEC_E_OK)
  221. {
  222. if ((uint)result == SEC_E_INVALID_HANDLE)
  223. {
  224. throw new Exception("InitializeSecurityContext failed, invalid handle");
  225. }
  226. else if ((uint)result == SEC_E_BUFFER_TOO_SMALL)
  227. {
  228. throw new Exception("InitializeSecurityContext failed, buffer too small");
  229. }
  230. else
  231. {
  232. throw new Exception("InitializeSecurityContext failed, error code " + ((uint)result).ToString("X"));
  233. }
  234. }
  235. byte[] messageBytes = outputBuffer.GetBufferBytes();
  236. inputBuffer.Dispose();
  237. input.Dispose();
  238. outputBuffer.Dispose();
  239. output.Dispose();
  240. return messageBytes;
  241. }
  242. public static byte[] GetType2Message(byte[] type1MessageBytes, out SecHandle serverContext)
  243. {
  244. SecHandle credentialsHandle = AcquireNTLMCredentialsHandle();
  245. SecBuffer inputBuffer = new SecBuffer(type1MessageBytes);
  246. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  247. serverContext = new SecHandle();
  248. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  249. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  250. uint contextAttributes;
  251. SECURITY_INTEGER timestamp;
  252. int 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);
  253. if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED)
  254. {
  255. if ((uint)result == SEC_E_INVALID_HANDLE)
  256. {
  257. throw new Exception("AcceptSecurityContext failed, invalid handle");
  258. }
  259. else if ((uint)result == SEC_E_BUFFER_TOO_SMALL)
  260. {
  261. throw new Exception("AcceptSecurityContext failed, buffer too small");
  262. }
  263. else
  264. {
  265. throw new Exception("AcceptSecurityContext failed, error code " + ((uint)result).ToString("X"));
  266. }
  267. }
  268. FreeCredentialsHandle(ref credentialsHandle);
  269. byte[] messageBytes = outputBuffer.GetBufferBytes();
  270. inputBuffer.Dispose();
  271. input.Dispose();
  272. outputBuffer.Dispose();
  273. output.Dispose();
  274. return messageBytes;
  275. }
  276. /// <summary>
  277. /// AcceptSecurityContext will return SEC_E_LOGON_DENIED when the password is correct in these cases:
  278. /// 1. The account is listed under the "Deny access to this computer from the network" list.
  279. /// 2. 'limitblankpassworduse' is set to 1, non-guest is attempting to login with an empty password,
  280. /// and the Guest account is disabled, has non-empty pasword set or listed under the "Deny access to this computer from the network" list.
  281. /// </summary>
  282. /// <remarks>
  283. /// 1. 'limitblankpassworduse' will not affect the Guest account.
  284. /// 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.
  285. /// - 'limitblankpassworduse' is set to 1.
  286. /// - The user has an empty password set.
  287. /// - Guest is NOT listed in the "Deny access to this computer from the network" list.
  288. /// - Guest is enabled and has empty pasword set.
  289. /// </remarks>
  290. public static bool AuthenticateType3Message(SecHandle serverContext, byte[] type3MessageBytes)
  291. {
  292. SecHandle newContext = new SecHandle();
  293. SecBuffer inputBuffer = new SecBuffer(type3MessageBytes);
  294. SecBufferDesc input = new SecBufferDesc(inputBuffer);
  295. SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE);
  296. SecBufferDesc output = new SecBufferDesc(outputBuffer);
  297. uint contextAttributes;
  298. SECURITY_INTEGER timestamp;
  299. int 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);
  300. inputBuffer.Dispose();
  301. input.Dispose();
  302. outputBuffer.Dispose();
  303. output.Dispose();
  304. if (result == SEC_E_OK)
  305. {
  306. return true;
  307. }
  308. else if ((uint)result == SEC_E_LOGON_DENIED)
  309. {
  310. return false;
  311. }
  312. else
  313. {
  314. if ((uint)result == SEC_E_INVALID_TOKEN)
  315. {
  316. throw new Exception("AcceptSecurityContext failed, invalid security token");
  317. }
  318. else
  319. {
  320. throw new Exception("AcceptSecurityContext failed, error code " + ((uint)result).ToString("X"));
  321. }
  322. }
  323. }
  324. }
  325. }