FreeRDP
Loading...
Searching...
No Matches
TestSspiPasswordHash.c
1#include <winpr/crt.h>
2#include <winpr/sspi.h>
3#include <winpr/winpr.h>
4
5static const char* TEST_USER = "Username";
6static const char* TEST_DOMAIN = "Domain";
7
8static SECURITY_STATUS run_client_exchange(SecurityFunctionTable* table, ULONG cbMaxToken,
9 const char* password, UINT32 extra_identity_flags)
10{
11 SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
12 CredHandle clientCreds = WINPR_C_ARRAY_INIT;
13 CredHandle serverCreds = WINPR_C_ARRAY_INIT;
14 CtxtHandle clientCtx = WINPR_C_ARRAY_INIT;
15 CtxtHandle serverCtx = WINPR_C_ARRAY_INIT;
16 TimeStamp expiration = WINPR_C_ARRAY_INIT;
17 SEC_WINNT_AUTH_IDENTITY identity = WINPR_C_ARRAY_INIT;
18 SecBuffer clientOut = WINPR_C_ARRAY_INIT;
19 SecBuffer serverOut = WINPR_C_ARRAY_INIT;
20 SecBufferDesc clientOutDesc = { SECBUFFER_VERSION, 1, &clientOut };
21 SecBufferDesc serverOutDesc = { SECBUFFER_VERSION, 1, &serverOut };
22 /* Each side reads what the other just wrote, so input descs alias the peer's output buffer. */
23 SecBufferDesc serverInDesc = { SECBUFFER_VERSION, 1, &clientOut };
24 SecBufferDesc clientInDesc = { SECBUFFER_VERSION, 1, &serverOut };
25 ULONG attrs = 0;
26 const ULONG fContextReq =
27 ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY;
28 const ULONG fAcceptReq = ASC_REQ_MUTUAL_AUTH | ASC_REQ_CONFIDENTIALITY | ASC_REQ_CONNECTION;
29
30 SecInvalidateHandle(&clientCtx);
31 SecInvalidateHandle(&serverCtx);
32
33 if (sspi_SetAuthIdentity(&identity, TEST_USER, TEST_DOMAIN, password) < 0)
34 goto fail;
35
36 identity.Flags |= extra_identity_flags;
37
38 status =
39 table->AcquireCredentialsHandle(nullptr, NTLM_SSP_NAME, SECPKG_CRED_OUTBOUND, nullptr,
40 &identity, nullptr, nullptr, &clientCreds, &expiration);
41 if (status != SEC_E_OK)
42 goto fail;
43
44 status = table->AcquireCredentialsHandle(nullptr, NTLM_SSP_NAME, SECPKG_CRED_INBOUND, nullptr,
45 nullptr, nullptr, nullptr, &serverCreds, &expiration);
46 if (status != SEC_E_OK)
47 goto fail;
48
49 /* Client -> Negotiate */
50 clientOut.BufferType = SECBUFFER_TOKEN;
51 clientOut.cbBuffer = cbMaxToken;
52 clientOut.pvBuffer = malloc(cbMaxToken);
53 if (!clientOut.pvBuffer)
54 {
55 status = SEC_E_INSUFFICIENT_MEMORY;
56 goto fail;
57 }
58
59 status = table->InitializeSecurityContext(&clientCreds, nullptr, nullptr, fContextReq, 0,
60 SECURITY_NATIVE_DREP, nullptr, 0, &clientCtx,
61 &clientOutDesc, &attrs, &expiration);
62 if (status != SEC_I_CONTINUE_NEEDED)
63 goto fail;
64
65 /* Server <- Negotiate, Server -> Challenge */
66 serverOut.BufferType = SECBUFFER_TOKEN;
67 serverOut.cbBuffer = cbMaxToken;
68 serverOut.pvBuffer = malloc(cbMaxToken);
69 if (!serverOut.pvBuffer)
70 {
71 status = SEC_E_INSUFFICIENT_MEMORY;
72 goto fail;
73 }
74
75 status = table->AcceptSecurityContext(&serverCreds, nullptr, &serverInDesc, fAcceptReq,
76 SECURITY_NATIVE_DREP, &serverCtx, &serverOutDesc, &attrs,
77 &expiration);
78 if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK)
79 goto fail;
80
81 /* Client <- Challenge, Client -> Authenticate */
82 free(clientOut.pvBuffer);
83 clientOut.cbBuffer = cbMaxToken;
84 clientOut.pvBuffer = malloc(cbMaxToken);
85 if (!clientOut.pvBuffer)
86 {
87 status = SEC_E_INSUFFICIENT_MEMORY;
88 goto fail;
89 }
90
91 status = table->InitializeSecurityContext(&clientCreds, &clientCtx, nullptr, fContextReq, 0,
92 SECURITY_NATIVE_DREP, &clientInDesc, 0, &clientCtx,
93 &clientOutDesc, &attrs, &expiration);
94
95fail:
96 free(clientOut.pvBuffer);
97 free(serverOut.pvBuffer);
98 if (SecIsValidHandle(&clientCtx))
99 (void)table->DeleteSecurityContext(&clientCtx);
100 if (SecIsValidHandle(&serverCtx))
101 (void)table->DeleteSecurityContext(&serverCtx);
102 if (SecIsValidHandle(&clientCreds))
103 (void)table->FreeCredentialsHandle(&clientCreds);
104 if (SecIsValidHandle(&serverCreds))
105 (void)table->FreeCredentialsHandle(&serverCreds);
106 sspi_FreeAuthIdentity(&identity);
107 return status;
108}
109
110static BOOL status_is_success(SECURITY_STATUS status)
111{
112 switch (status)
113 {
114 case SEC_E_OK:
115 case SEC_I_CONTINUE_NEEDED:
116 case SEC_I_COMPLETE_NEEDED:
117 case SEC_I_COMPLETE_AND_CONTINUE:
118 return TRUE;
119 default:
120 return FALSE;
121 }
122}
123
124/* Long plaintext password (>512 chars) must not be misinterpreted as a pass-the-hash hex blob. */
125static BOOL test_long_plaintext_password(SecurityFunctionTable* table, ULONG cbMaxToken)
126{
127 char long_password[1025];
128 memset(long_password, 'A', sizeof(long_password) - 1);
129 long_password[sizeof(long_password) - 1] = '\0';
130
131 const SECURITY_STATUS status = run_client_exchange(table, cbMaxToken, long_password, 0);
132 if (status == SEC_E_NO_CREDENTIALS)
133 {
134 printf("FAIL: long plaintext password produced SEC_E_NO_CREDENTIALS\n");
135 return FALSE;
136 }
137 if (!status_is_success(status))
138 {
139 printf("FAIL: long plaintext password rejected with 0x%08" PRIX32 " (%s)\n", status,
140 GetSecurityStatusString(status));
141 return FALSE;
142 }
143 return TRUE;
144}
145
146/* 32-hex-char password tagged with SEC_WINPR_AUTH_IDENTITY_PASSWORD_HASH must route through the
147 * pass-the-hash path. */
148static BOOL test_password_hash_flag(SecurityFunctionTable* table, ULONG cbMaxToken)
149{
150 const char* hash_hex = "d5922a65c4d5c082ca444af1be0001db";
151
152 const SECURITY_STATUS status =
153 run_client_exchange(table, cbMaxToken, hash_hex, SEC_WINPR_AUTH_IDENTITY_PASSWORD_HASH);
154 if (!status_is_success(status))
155 {
156 printf("FAIL: pass-the-hash with explicit flag rejected with 0x%08" PRIX32 " (%s)\n",
157 status, GetSecurityStatusString(status));
158 return FALSE;
159 }
160 return TRUE;
161}
162
163int TestSspiPasswordHash(int argc, char* argv[])
164{
165 int rc = -1;
166 SecPkgInfo* packageInfo = nullptr;
167
168 WINPR_UNUSED(argc);
169 WINPR_UNUSED(argv);
170
171 sspi_GlobalInit();
172 SecurityFunctionTable* table = InitSecurityInterfaceEx(SSPI_INTERFACE_WINPR);
173 if (!table)
174 goto cleanup;
175
176 if (table->QuerySecurityPackageInfo(NTLM_SSP_NAME, &packageInfo) != SEC_E_OK)
177 goto cleanup;
178
179 const ULONG cbMaxToken = packageInfo->cbMaxToken;
180
181 if (!test_long_plaintext_password(table, cbMaxToken))
182 goto cleanup;
183
184 if (!test_password_hash_flag(table, cbMaxToken))
185 goto cleanup;
186
187 rc = 0;
188
189cleanup:
190 if (packageInfo)
191 (void)table->FreeContextBuffer(packageInfo);
192 sspi_GlobalFinish();
193 return rc;
194}