1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Implementation of Windows authentication package building functions need
6 // to create the authentication packages used to sign the user into a Windows
7 // system.
8
9 #include <vector>
10
11 #include "chrome/credential_provider/gaiacp/auth_utils.h"
12
13 #include "base/callback.h"
14 #include "base/callback_helpers.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
18 #include "chrome/credential_provider/gaiacp/logging.h"
19 #include "chrome/credential_provider/gaiacp/os_user_manager.h"
20
21 namespace credential_provider {
22
23 namespace {
24
25 // If |password| should be encrypted, fills |protected_password| with a copy
26 // encrypted with CredProtect, otherwise just copies |password| to
27 // |protected_password|.
28 //
29 // CredProtectW and CredIsProtectedW expect a non-const password so |password|
30 // is passed in as a non-const pointer.
ProtectIfNecessaryAndCopyPassword(wchar_t * password,CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,std::vector<wchar_t> * protected_password)31 HRESULT ProtectIfNecessaryAndCopyPassword(
32 wchar_t* password,
33 CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
34 std::vector<wchar_t>* protected_password) {
35 DCHECK(protected_password);
36 DCHECK(password);
37
38 protected_password->clear();
39
40 if (cpus != CPUS_UNLOCK_WORKSTATION && cpus != CPUS_LOGON)
41 return E_UNEXPECTED;
42
43 DWORD password_length = static_cast<DWORD>(wcslen(password));
44
45 // ProtectAndCopyString is intended for non-empty strings only. Empty
46 // passwords do not need to be encrypted.
47 if (!password_length)
48 return S_OK;
49
50 CRED_PROTECTION_TYPE protection_type;
51 // Determine if password can be encrypted.
52 // Encryption should be skipped if the password is already encrypted.
53 // An encrypted password may be received through SetSerialization in the
54 // CPUS_LOGON scenario during a Terminal Services connection, for
55 // instance.
56 if ((!::CredIsProtectedW(password, &protection_type) ||
57 CredUnprotected != protection_type)) {
58 protected_password->resize(password_length + 1);
59 wcscpy_s(&(*protected_password)[0], password_length + 1, password);
60 return S_OK;
61 }
62
63 // Determine the size of the buffer needed to generate the protected string.
64 //
65 // Note that the third parameter to CredProtect, the number of characters of
66 // password to encrypt, must include the NULL terminator.
67 DWORD needed_length = 0;
68 if (::CredProtectW(FALSE, password, password_length + 1, nullptr,
69 &needed_length, nullptr)) {
70 LOGFN(ERROR) << "Failed to get required length of protected string.";
71 return E_UNEXPECTED;
72 }
73
74 DWORD last_error = GetLastError();
75 if ((ERROR_INSUFFICIENT_BUFFER != last_error) || (0 == needed_length)) {
76 LOGFN(ERROR) << "Unexpected error when getting length of proceted string.";
77 return HRESULT_FROM_WIN32(last_error);
78 }
79
80 protected_password->resize(needed_length);
81 if (!::CredProtectW(FALSE, password, password_length + 1,
82 &(*protected_password)[0], &needed_length, nullptr)) {
83 LOGFN(ERROR) << "Failed to protect string.";
84 protected_password->clear();
85 return HRESULT_FROM_WIN32(::GetLastError());
86 }
87
88 return S_OK;
89 }
90
91 // The following function is intended to be used ONLY with the Kerb*Pack
92 // functions. It does no bounds-checking because its callers have precise
93 // requirements and are written to respect its limitations. You can read more
94 // about the UNICODE_STRING type at:
95 // https://docs.microsoft.com/en-us/windows/desktop/api/subauth/ns-subauth-_unicode_string
UnicodeStringPackedUnicodeStringCopy(const UNICODE_STRING & rus,wchar_t * buffer,UNICODE_STRING * pus)96 void UnicodeStringPackedUnicodeStringCopy(const UNICODE_STRING& rus,
97 wchar_t* buffer,
98 UNICODE_STRING* pus) {
99 pus->Length = rus.Length;
100 pus->MaximumLength = rus.Length;
101 pus->Buffer = buffer;
102
103 CopyMemory(pus->Buffer, rus.Buffer, pus->Length);
104 }
105
106 // Initialize the members of a KERB_INTERACTIVE_UNLOCK_LOGON with weak
107 // references to the passed-in strings. Later KerbInteractiveUnlockLogonPack
108 // will be used to serialize the structure so the strings will not need to
109 // be copied.
110 //
111 // The password is stored in encrypted form for CPUS_LOGON and
112 // CPUS_UNLOCK_WORKSTATION because the system can accept encrypted credentials.
113 // For all other scenarios, this function will simply fail.
KerbInteractiveUnlockLogonInit(wchar_t * domain,wchar_t * username,wchar_t * password,CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,KERB_INTERACTIVE_UNLOCK_LOGON * pkiul)114 void KerbInteractiveUnlockLogonInit(wchar_t* domain,
115 wchar_t* username,
116 wchar_t* password,
117 CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
118 KERB_INTERACTIVE_UNLOCK_LOGON* pkiul) {
119 DCHECK(domain);
120 DCHECK(username);
121 DCHECK(password);
122 DCHECK(pkiul);
123
124 ZeroMemory(pkiul, sizeof(KERB_INTERACTIVE_UNLOCK_LOGON));
125
126 KERB_INTERACTIVE_LOGON* pkil = &pkiul->Logon;
127
128 // Note: this method uses custom logic to pack a KERB_INTERACTIVE_UNLOCK_LOGON
129 // with a serialized credential. We could replace the calls to
130 // InitWindowsStringWithString and KerbInteractiveUnlockLogonPack with a
131 // single call to CredPackAuthenticationBuffer, but that API has a drawback:
132 // it returns a KERB_INTERACTIVE_UNLOCK_LOGON whose MessageType is always
133 // KerbInteractiveLogon.
134 //
135 // If we only handled CPUS_LOGON, this drawback would not be a problem. For
136 // CPUS_UNLOCK_WORKSTATION, we could cast the output buffer of
137 // CredPackAuthenticationBuffer to KERB_INTERACTIVE_UNLOCK_LOGON and modify
138 // the MessageType to KerbWorkstationUnlockLogon, but such a cast would be
139 // unsupported -- the output format of CredPackAuthenticationBuffer is not
140 // officially documented.
141
142 // Set a MessageType based on the usage scenario.
143 switch (cpus) {
144 case CPUS_UNLOCK_WORKSTATION:
145 pkil->MessageType = KerbWorkstationUnlockLogon;
146 break;
147 case CPUS_LOGON:
148 pkil->MessageType = KerbInteractiveLogon;
149 break;
150 default:
151 NOTREACHED();
152 return;
153 }
154
155 // Initialize the UNICODE_STRINGS to share domain, username and password
156 // strings.
157 InitWindowsStringWithString(domain, &pkil->LogonDomainName);
158 InitWindowsStringWithString(username, &pkil->UserName);
159 InitWindowsStringWithString(password, &pkil->Password);
160 }
161
162 // WinLogon and LSA consume "packed" KERB_INTERACTIVE_UNLOCK_LOGONs. In these,
163 // the PWSTR members of each UNICODE_STRING are not actually pointers but byte
164 // offsets into the overall buffer represented by the packed
165 // KERB_INTERACTIVE_UNLOCK_LOGON. For example:
166 //
167 // Length is in bytes, not characters
168 // input_logon.Logon.LogonDomainName.Length = 14;
169 // LogonDomainName begins immediately after the KERB_... struct in the buffer
170 // input_logon.Logon.LogonDomainName.Buffer =
171 // sizeof(KERB_INTERACTIVE_UNLOCK_LOGON);
172 // input_logon.Logon.UserName.Length = 10
173 // input_logon.Logon.UserName.Buffer = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) +
174 // 14 -> UNICODE_STRINGS are NOT null-terminated
175 //
176 // input_logon.Logon.Password.Length = 16
177 // input_logon.Logon.Password.Buffer = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) +
178 // 14 + 10
KerbInteractiveUnlockLogonPack(const KERB_INTERACTIVE_UNLOCK_LOGON & input_unlock_logon,BYTE ** prgb,DWORD * pcb)179 HRESULT KerbInteractiveUnlockLogonPack(
180 const KERB_INTERACTIVE_UNLOCK_LOGON& input_unlock_logon,
181 BYTE** prgb,
182 DWORD* pcb) {
183 DCHECK(prgb);
184 DCHECK(pcb);
185 const KERB_INTERACTIVE_LOGON* input_logon = &input_unlock_logon.Logon;
186
187 // Allocate space for struct plus extra for the three strings.
188 DWORD cb = sizeof(input_unlock_logon) + input_logon->LogonDomainName.Length +
189 input_logon->UserName.Length + input_logon->Password.Length;
190
191 KERB_INTERACTIVE_UNLOCK_LOGON* output_unlock_logon =
192 reinterpret_cast<KERB_INTERACTIVE_UNLOCK_LOGON*>(::CoTaskMemAlloc(cb));
193 if (!output_unlock_logon) {
194 LOGFN(ERROR) << "Failed to allocate kerberos logon package.";
195 return E_OUTOFMEMORY;
196 }
197
198 ::SecureZeroMemory(&output_unlock_logon->LogonId,
199 sizeof(output_unlock_logon->LogonId));
200
201 // Point output_buffer at the beginning of the extra space.
202 BYTE* output_buffer = reinterpret_cast<BYTE*>(output_unlock_logon) +
203 sizeof(*output_unlock_logon);
204
205 // Set up the Logon structure within the KERB_INTERACTIVE_UNLOCK_LOGON.
206 KERB_INTERACTIVE_LOGON* output_logon = &output_unlock_logon->Logon;
207
208 output_logon->MessageType = input_logon->MessageType;
209
210 // For each string: fix up appropriate buffer pointer to be offset, advance
211 // buffer pointer over copied characters in extra space.
212 UnicodeStringPackedUnicodeStringCopy(input_logon->LogonDomainName,
213 (wchar_t*)output_buffer,
214 &output_logon->LogonDomainName);
215 output_logon->LogonDomainName.Buffer =
216 reinterpret_cast<wchar_t*>(output_buffer - (BYTE*)output_unlock_logon);
217 output_buffer += output_logon->LogonDomainName.Length;
218
219 UnicodeStringPackedUnicodeStringCopy(
220 input_logon->UserName, reinterpret_cast<wchar_t*>(output_buffer),
221 &output_logon->UserName);
222 output_logon->UserName.Buffer =
223 reinterpret_cast<wchar_t*>(output_buffer - (BYTE*)output_unlock_logon);
224 output_buffer += output_logon->UserName.Length;
225
226 UnicodeStringPackedUnicodeStringCopy(
227 input_logon->Password, reinterpret_cast<wchar_t*>(output_buffer),
228 &output_logon->Password);
229 output_logon->Password.Buffer =
230 reinterpret_cast<wchar_t*>(output_buffer - (BYTE*)output_unlock_logon);
231
232 *prgb = (BYTE*)output_unlock_logon;
233 *pcb = cb;
234
235 return S_OK;
236 }
237
UnpackUserInfoFromAuthenticationBuffer(const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION * cpcs,base::string16 * domain,base::string16 * username)238 HRESULT UnpackUserInfoFromAuthenticationBuffer(
239 const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* cpcs,
240 base::string16* domain,
241 base::string16* username) {
242 DCHECK(cpcs);
243 DCHECK(domain);
244 DCHECK(username);
245 domain->clear();
246 username->clear();
247 KERB_INTERACTIVE_UNLOCK_LOGON* pkiul =
248 reinterpret_cast<KERB_INTERACTIVE_UNLOCK_LOGON*>(cpcs->rgbSerialization);
249 ULONG buffer_size = cpcs->cbSerialization;
250 KERB_INTERACTIVE_LOGON* pkil = &pkiul->Logon;
251
252 base::string16 serialization_domain;
253 base::string16 serialization_username;
254 // Check to see if the buffer is packed:
255 // 1. Ensure that the buffer can possibly contain the serialization.
256 // 2. Also if the range described by each (Buffer + MaximumSize) falls
257 // within the total bytecount, we can be pretty confident that the Buffers
258 // are actually offsets and that this is a packed credential.
259 if (sizeof(*pkiul) <= buffer_size &&
260 (reinterpret_cast<ptrdiff_t>(pkil->LogonDomainName.Buffer) +
261 pkil->LogonDomainName.MaximumLength <=
262 static_cast<ptrdiff_t>(buffer_size)) &&
263 (reinterpret_cast<ptrdiff_t>(pkil->UserName.Buffer) +
264 pkil->UserName.MaximumLength <=
265 static_cast<ptrdiff_t>(buffer_size)) &&
266 (reinterpret_cast<ptrdiff_t>(pkil->Password.Buffer) +
267 pkil->Password.MaximumLength <=
268 static_cast<ptrdiff_t>(buffer_size))) {
269 // When the authentication package is packed, then each buffer is not
270 // null terminated and the next buffer starts at the end of the previous
271 // one. Also the buffers are stored in the order that they are declared
272 // in the structure.
273
274 if (pkil->LogonDomainName.Buffer && pkil->UserName.Buffer) {
275 const wchar_t* domain_buffer_pos = reinterpret_cast<wchar_t*>(
276 (reinterpret_cast<ptrdiff_t>(pkiul) +
277 reinterpret_cast<ptrdiff_t>(pkil->LogonDomainName.Buffer)));
278
279 const wchar_t* username_buffer_pos = reinterpret_cast<wchar_t*>(
280 (reinterpret_cast<ptrdiff_t>(pkiul) +
281 reinterpret_cast<ptrdiff_t>(pkil->UserName.Buffer)));
282 serialization_domain = base::string16(
283 domain_buffer_pos,
284 pkil->LogonDomainName.MaximumLength / sizeof(domain_buffer_pos[0]));
285 serialization_username = base::string16(
286 username_buffer_pos,
287 pkil->UserName.MaximumLength / sizeof(username_buffer_pos[0]));
288 }
289 } else {
290 // If the authentication package is not packed, assume that the buffer
291 // points to a null terminated string.
292 serialization_domain = pkiul->Logon.LogonDomainName.Buffer;
293 serialization_username = pkiul->Logon.UserName.Buffer;
294 }
295
296 if (serialization_domain.length() && serialization_username.length()) {
297 *domain = serialization_domain;
298 *username = serialization_username;
299 return S_OK;
300 }
301 return E_FAIL;
302 }
303
304 } // namespace
305
306 // Gets the auth package id for NEGOSSP_NAME_A.
GetAuthenticationPackageId(ULONG * id)307 HRESULT GetAuthenticationPackageId(ULONG* id) {
308 DCHECK(id);
309
310 HANDLE lsa;
311 NTSTATUS status = ::LsaConnectUntrusted(&lsa);
312 HRESULT hr = HRESULT_FROM_NT(status);
313 if (FAILED(hr)) {
314 LOGFN(ERROR) << "LsaConnectUntrusted hr=" << putHR(hr);
315 return hr;
316 }
317
318 LSA_STRING name;
319 InitWindowsStringWithString(NEGOSSP_NAME_A, &name);
320
321 status = ::LsaLookupAuthenticationPackage(lsa, &name, id);
322 ::LsaDeregisterLogonProcess(lsa);
323 hr = HRESULT_FROM_NT(status);
324 if (FAILED(hr))
325 LOGFN(ERROR) << "LsaLookupAuthenticationPackage hr=" << putHR(hr);
326
327 return hr;
328 }
329
DetermineUserSidFromAuthenticationBuffer(const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION * cpcs,base::string16 * sid)330 HRESULT DetermineUserSidFromAuthenticationBuffer(
331 const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* cpcs,
332 base::string16* sid) {
333 DCHECK(sid);
334
335 sid->clear();
336
337 base::string16 serialization_domain;
338 base::string16 serialization_username;
339 HRESULT hr = UnpackUserInfoFromAuthenticationBuffer(
340 cpcs, &serialization_domain, &serialization_username);
341
342 if (SUCCEEDED(hr)) {
343 hr = OSUserManager::Get()->GetUserSID(serialization_domain.c_str(),
344 serialization_username.c_str(), sid);
345 // The function GetUserSID strictly checks the domain that is passed in and
346 // determines if a user really exists in that domain. However the behavior
347 // of RDP connections is more lenient. You can enter any domain you want
348 // when connecting and if a user in that specific domain is not found, RDP
349 // will fall back to trying to sign in the local user with the same
350 // username. To emulate that behavior, we try to also check if there is a
351 // user on the local domain we could possibly signin and return the SID for
352 // that user if it exists.
353 if (FAILED(hr)) {
354 base::string16 local_domain = OSUserManager::GetLocalDomain();
355 if (!base::EqualsCaseInsensitiveASCII(local_domain,
356 serialization_domain)) {
357 hr = OSUserManager::Get()->GetUserSID(
358 local_domain.c_str(), serialization_username.c_str(), sid);
359
360 if (FAILED(hr)) {
361 LOGFN(ERROR) << "GetUserSID hr=" << putHR(hr);
362 return hr;
363 }
364 }
365 }
366 }
367 return S_OK;
368 }
369
BuildCredPackAuthenticationBuffer(BSTR domain,BSTR username,BSTR password,CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION * cpcs)370 HRESULT BuildCredPackAuthenticationBuffer(
371 BSTR domain,
372 BSTR username,
373 BSTR password,
374 CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
375 CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* cpcs) {
376 DCHECK(username);
377 DCHECK(password);
378 DCHECK(cpcs);
379
380 HRESULT hr = GetAuthenticationPackageId(&(cpcs->ulAuthenticationPackage));
381 if (FAILED(hr)) {
382 LOGFN(ERROR) << "GetAuthenticationPackageId hr=" << putHR(hr);
383 return hr;
384 }
385
386 // Caller should fill this in.
387 cpcs->clsidCredentialProvider = GUID_NULL;
388
389 std::vector<wchar_t> protected_password;
390 // Copy the password and pass the copied buffer into
391 // ProtectIfNecessaryAndCopyPassword since it expects a non-const input
392 // buffer.
393 std::vector<wchar_t> copy_password(OLE2W(password),
394 OLE2W(password) + wcslen(password) + 1);
395 hr = ProtectIfNecessaryAndCopyPassword(©_password[0], cpus,
396 &protected_password);
397
398 // Zero out the unencrypted copy of the password.
399 SecurelyClearBuffer(©_password[0], copy_password.size());
400 if (FAILED(hr)) {
401 LOGFN(ERROR) << "ProtectIfNecessaryAndCopyPassword hr=" << putHR(hr);
402 return hr;
403 }
404
405 // Protected password may still be insecure so make sure to zero it out.
406 base::ScopedClosureRunner zero_buffer_on_exit(
407 base::BindOnce(base::IgnoreResult(&SecurelyClearBuffer),
408 &protected_password[0], protected_password.size()));
409
410 wchar_t* logon_domain = domain;
411
412 // For domain joined users, use a Kerberos authentication package.
413 KERB_INTERACTIVE_UNLOCK_LOGON kiul;
414 KerbInteractiveUnlockLogonInit(OLE2W(logon_domain), OLE2W(username),
415 &protected_password[0], cpus, &kiul);
416
417 // Use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon
418 // scenarios. It contains a KERB_INTERACTIVE_LOGON to hold the creds
419 // plus a LUID that is filled in for us by Winlogon as necessary.
420 hr = KerbInteractiveUnlockLogonPack(kiul, &cpcs->rgbSerialization,
421 &cpcs->cbSerialization);
422 if (FAILED(hr)) {
423 hr = HRESULT_FROM_WIN32(::GetLastError());
424 LOGFN(ERROR) << "KerbInteractiveUnlockLogonPack user=" << username
425 << " dn=" << logon_domain << " hr=" << putHR(hr)
426 << " length=" << cpcs->cbSerialization;
427 return hr;
428 }
429
430 return S_OK;
431 }
432
433 } // namespace credential_provider
434