Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / ssl / OSReauthenticator.cpp
blobb63b8d557fc0f0be3bacc7a8cb0a6ee3d1e0e367
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OSReauthenticator.h"
9 #include "OSKeyStore.h"
10 #include "nsNetCID.h"
11 #include "mozilla/dom/Promise.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/Preferences.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsIBaseWindow.h"
17 #include "nsIDocShell.h"
18 #include "nsISupportsUtils.h"
19 #include "nsIWidget.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsThreadUtils.h"
23 #include "mozilla/ipc/IPCTypes.h"
25 NS_IMPL_ISUPPORTS(OSReauthenticator, nsIOSReauthenticator)
27 extern mozilla::LazyLogModule gCredentialManagerSecretLog;
29 using mozilla::LogLevel;
30 using mozilla::Maybe;
31 using mozilla::Preferences;
32 using mozilla::WindowsHandle;
33 using mozilla::dom::Promise;
35 #define PREF_BLANK_PASSWORD "security.osreauthenticator.blank_password"
36 #define PREF_PASSWORD_LAST_CHANGED_LO \
37 "security.osreauthenticator.password_last_changed_lo"
38 #define PREF_PASSWORD_LAST_CHANGED_HI \
39 "security.osreauthenticator.password_last_changed_hi"
41 #if defined(XP_WIN)
42 # include <combaseapi.h>
43 # include <ntsecapi.h>
44 # include <wincred.h>
45 # include <windows.h>
46 # include "nsIWindowsRegKey.h" // Must be included after <windows.h> for HKEY definition
47 # define SECURITY_WIN32
48 # include <security.h>
49 # include <shlwapi.h>
50 # if !defined(__MINGW32__)
51 # include <Lm.h>
52 # undef ACCESS_READ // nsWindowsRegKey defines its own ACCESS_READ
53 # endif // !defined(__MINGW32__)
54 struct HandleCloser {
55 typedef HANDLE pointer;
56 void operator()(HANDLE h) {
57 if (h != INVALID_HANDLE_VALUE) {
58 CloseHandle(h);
62 struct BufferFreer {
63 typedef LPVOID pointer;
64 ULONG mSize;
65 explicit BufferFreer(ULONG size) : mSize(size) {}
66 void operator()(LPVOID b) {
67 SecureZeroMemory(b, mSize);
68 CoTaskMemFree(b);
71 struct LsaDeregistrator {
72 typedef HANDLE pointer;
73 void operator()(HANDLE h) {
74 if (h != INVALID_HANDLE_VALUE) {
75 LsaDeregisterLogonProcess(h);
79 typedef std::unique_ptr<HANDLE, HandleCloser> ScopedHANDLE;
80 typedef std::unique_ptr<LPVOID, BufferFreer> ScopedBuffer;
81 typedef std::unique_ptr<HANDLE, LsaDeregistrator> ScopedLsaHANDLE;
83 constexpr int64_t Int32Modulo = 2147483648;
85 // Get the token info holding the sid.
86 std::unique_ptr<char[]> GetTokenInfo(ScopedHANDLE& token) {
87 DWORD length = 0;
88 // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
89 mozilla::Unused << GetTokenInformation(token.get(), TokenUser, nullptr, 0,
90 &length);
91 if (!length || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
92 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
93 ("Unable to obtain current token info."));
94 return nullptr;
96 std::unique_ptr<char[]> token_info(new char[length]);
97 if (!GetTokenInformation(token.get(), TokenUser, token_info.get(), length,
98 &length)) {
99 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
100 ("Unable to obtain current token info (second call, possible "
101 "system error."));
102 return nullptr;
104 return token_info;
107 std::unique_ptr<char[]> GetUserTokenInfo() {
108 // Get current user sid to make sure the same user got logged in.
109 HANDLE token;
110 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
111 // Couldn't get a process token. This will fail any unlock attempts later.
112 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
113 ("Unable to obtain process token."));
114 return nullptr;
116 ScopedHANDLE scopedToken(token);
117 return GetTokenInfo(scopedToken);
120 Maybe<int64_t> GetPasswordLastChanged(const WCHAR* username) {
121 # if defined(__MINGW32__)
122 // NetUserGetInfo requires Lm.h which is not provided in MinGW builds
123 return mozilla::Nothing();
124 # else
125 LPUSER_INFO_1 user_info = NULL;
126 DWORD passwordAgeInSeconds = 0;
128 NET_API_STATUS ret =
129 NetUserGetInfo(NULL, username, 1, reinterpret_cast<LPBYTE*>(&user_info));
131 if (ret == NERR_Success) {
132 // Returns seconds since last password change.
133 passwordAgeInSeconds = user_info->usri1_password_age;
134 NetApiBufferFree(user_info);
135 } else {
136 return mozilla::Nothing();
139 // Return the time that the password was changed so we can use this
140 // for future comparisons.
141 return mozilla::Some(PR_Now() - passwordAgeInSeconds * PR_USEC_PER_SEC);
142 # endif
145 bool IsAutoAdminLogonEnabled() {
146 // https://support.microsoft.com/en-us/help/324737/how-to-turn-on-automatic-logon-in-windows
147 nsresult rv;
148 nsCOMPtr<nsIWindowsRegKey> regKey =
149 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
150 if (NS_FAILED(rv)) {
151 return false;
154 rv = regKey->Open(
155 nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
156 nsLiteralString(
157 u"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
158 nsIWindowsRegKey::ACCESS_READ);
159 if (NS_FAILED(rv)) {
160 return false;
163 nsAutoString value;
164 rv = regKey->ReadStringValue(u"AutoAdminLogon"_ns, value);
165 if (NS_FAILED(rv)) {
166 return false;
168 regKey->Close();
170 return value.Equals(u"1"_ns);
173 bool IsRequireSignonEnabled() {
174 // https://docs.microsoft.com/en-us/windows-hardware/customize/power-settings/no-subgroup-settings-prompt-for-password-on-resume
175 nsresult rv;
176 nsCOMPtr<nsIWindowsRegKey> regKey =
177 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
178 if (NS_FAILED(rv)) {
179 return true;
182 rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
183 u"System\\CurrentControlSet\\Control\\Power\\User\\Power"
184 "Schemes"_ns,
185 nsIWindowsRegKey::ACCESS_READ);
186 if (NS_FAILED(rv)) {
187 return true;
190 nsAutoString activePowerScheme;
191 rv = regKey->ReadStringValue(u"ActivePowerScheme"_ns, activePowerScheme);
192 if (NS_FAILED(rv)) {
193 return true;
195 regKey->Close();
197 rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
198 u"System\\CurrentControlSet\\Control\\Power\\User\\Power"
199 "Schemes\\"_ns +
200 activePowerScheme +
201 u"\\0e796bdb-100d-47d6-a2d5-f7d2daa51f51"_ns,
202 nsIWindowsRegKey::ACCESS_READ);
203 if (NS_FAILED(rv)) {
204 return true;
207 uint32_t value;
208 rv = regKey->ReadIntValue(u"ACSettingIndex"_ns, &value);
209 if (NS_FAILED(rv)) {
210 return true;
212 regKey->Close();
214 return !!value;
217 // Use the Windows credential prompt to ask the user to authenticate the
218 // currently used account.
219 static nsresult ReauthenticateUserWindows(
220 const nsAString& aMessageText, const nsAString& aCaptionText,
221 const WindowsHandle& hwndParent,
222 /* out */ bool& reauthenticated,
223 /* inout */ bool& isBlankPassword,
224 /* inout */ int64_t& prefLastChanged,
225 /* out */ bool& isAutoAdminLogonEnabled,
226 /* out */ bool& isRequireSignonEnabled) {
227 reauthenticated = false;
228 isAutoAdminLogonEnabled = false;
229 isRequireSignonEnabled = true;
231 // Check if the user has a blank password before proceeding
232 DWORD usernameLength = CREDUI_MAX_USERNAME_LENGTH + 1;
233 WCHAR username[CREDUI_MAX_USERNAME_LENGTH + 1] = {0};
235 if (!GetUserNameEx(NameSamCompatible, username, &usernameLength)) {
236 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
237 ("Error getting username"));
238 return NS_ERROR_FAILURE;
241 if (!IsOS(OS_DOMAINMEMBER)) {
242 const WCHAR* usernameNoDomain = username;
243 // Don't include the domain portion of the username when calling LogonUser.
244 LPCWSTR backslash = wcschr(username, L'\\');
245 if (backslash) {
246 usernameNoDomain = backslash + 1;
249 Maybe<int64_t> lastChanged = GetPasswordLastChanged(usernameNoDomain);
250 if (lastChanged.isSome()) {
251 bool shouldCheckAgain = lastChanged.value() > prefLastChanged;
252 // Update the value stored in preferences
253 prefLastChanged = lastChanged.value();
255 if (shouldCheckAgain) {
256 HANDLE logonUserHandle = INVALID_HANDLE_VALUE;
257 bool result =
258 LogonUser(usernameNoDomain, L".", L"", LOGON32_LOGON_INTERACTIVE,
259 LOGON32_PROVIDER_DEFAULT, &logonUserHandle);
260 if (result) {
261 CloseHandle(logonUserHandle);
263 // ERROR_ACCOUNT_RESTRICTION: Indicates a referenced user name and
264 // authentication information are valid, but some user account
265 // restriction has prevented successful authentication (such as
266 // time-of-day restrictions).
267 reauthenticated = isBlankPassword =
268 (result || GetLastError() == ERROR_ACCOUNT_RESTRICTION);
269 } else if (isBlankPassword) {
270 reauthenticated = true;
273 if (reauthenticated) {
274 return NS_OK;
276 } else {
277 isBlankPassword = false;
279 } else {
280 // Update any preferences, assuming domain members do not have blank
281 // passwords
282 isBlankPassword = false;
285 isAutoAdminLogonEnabled = IsAutoAdminLogonEnabled();
287 isRequireSignonEnabled = IsRequireSignonEnabled();
289 // Is used in next iteration if the previous login failed.
290 DWORD err = 0;
291 std::unique_ptr<char[]> userTokenInfo = GetUserTokenInfo();
293 // CredUI prompt.
294 CREDUI_INFOW credui = {};
295 credui.cbSize = sizeof(credui);
296 credui.hwndParent = reinterpret_cast<HWND>(hwndParent);
297 const nsString& messageText = PromiseFlatString(aMessageText);
298 credui.pszMessageText = messageText.get();
299 const nsString& captionText = PromiseFlatString(aCaptionText);
300 credui.pszCaptionText = captionText.get();
301 credui.hbmBanner = nullptr; // ignored
303 while (!reauthenticated) {
304 HANDLE lsa = INVALID_HANDLE_VALUE;
305 // Get authentication handle for future user authentications.
306 // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted
307 if (LsaConnectUntrusted(&lsa) != ERROR_SUCCESS) {
308 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
309 ("Error acquiring lsa. Authentication attempts will fail."));
310 return NS_ERROR_FAILURE;
312 ScopedLsaHANDLE scopedLsa(lsa);
314 if (!userTokenInfo || lsa == INVALID_HANDLE_VALUE) {
315 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
316 ("Error setting up login and user token."));
317 return NS_ERROR_FAILURE;
320 ULONG authPackage = 0;
321 ULONG outCredSize = 0;
322 LPVOID outCredBuffer = nullptr;
324 // Get user's Windows credentials.
325 // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creduipromptforwindowscredentialsw
326 err = CredUIPromptForWindowsCredentialsW(
327 &credui, err, &authPackage, nullptr, 0, &outCredBuffer, &outCredSize,
328 nullptr, CREDUIWIN_ENUMERATE_CURRENT_USER);
329 ScopedBuffer scopedOutCredBuffer(outCredBuffer, BufferFreer(outCredSize));
330 if (err == ERROR_CANCELLED) {
331 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
332 ("Error getting authPackage for user login, user cancel."));
333 return NS_OK;
335 if (err != ERROR_SUCCESS) {
336 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
337 ("Error getting authPackage for user login."));
338 return NS_ERROR_FAILURE;
341 // Verify the credentials.
342 TOKEN_SOURCE source;
343 PCHAR contextName = const_cast<PCHAR>("Mozilla");
344 size_t nameLength =
345 std::min(TOKEN_SOURCE_LENGTH, static_cast<int>(strlen(contextName)));
346 // Note that the string must not be longer than TOKEN_SOURCE_LENGTH.
347 memcpy(source.SourceName, contextName, nameLength);
348 // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-allocatelocallyuniqueid
349 if (!AllocateLocallyUniqueId(&source.SourceIdentifier)) {
350 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
351 ("Error allocating ID for logon process."));
352 return NS_ERROR_FAILURE;
355 NTSTATUS substs;
356 void* profileBuffer = nullptr;
357 ULONG profileBufferLength = 0;
358 QUOTA_LIMITS limits = {0};
359 LUID luid;
360 HANDLE token = INVALID_HANDLE_VALUE;
361 LSA_STRING name;
362 name.Buffer = contextName;
363 name.Length = strlen(name.Buffer);
364 name.MaximumLength = name.Length;
365 // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsalogonuser
366 NTSTATUS sts = LsaLogonUser(
367 scopedLsa.get(), &name, (SECURITY_LOGON_TYPE)Interactive, authPackage,
368 scopedOutCredBuffer.get(), outCredSize, nullptr, &source,
369 &profileBuffer, &profileBufferLength, &luid, &token, &limits, &substs);
370 ScopedHANDLE scopedToken(token);
371 LsaFreeReturnBuffer(profileBuffer);
372 if (sts == ERROR_SUCCESS) {
373 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
374 ("User logged in successfully."));
375 } else {
376 err = LsaNtStatusToWinError(sts);
377 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
378 ("Login failed with %lx (%lx).", sts, err));
379 continue;
382 // The user can select any user to log-in on the authentication prompt.
383 // Make sure that the logged in user is the current user.
384 std::unique_ptr<char[]> logonTokenInfo = GetTokenInfo(scopedToken);
385 if (!logonTokenInfo) {
386 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
387 ("Error getting logon token info."));
388 return NS_ERROR_FAILURE;
390 PSID logonSID =
391 reinterpret_cast<TOKEN_USER*>(logonTokenInfo.get())->User.Sid;
392 PSID userSID = reinterpret_cast<TOKEN_USER*>(userTokenInfo.get())->User.Sid;
393 if (EqualSid(userSID, logonSID)) {
394 MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
395 ("Login successfully (correct user)."));
396 reauthenticated = true;
397 break;
398 } else {
399 err = ERROR_LOGON_FAILURE;
402 return NS_OK;
404 #endif // XP_WIN
406 static nsresult ReauthenticateUser(const nsAString& prompt,
407 const nsAString& caption,
408 const WindowsHandle& hwndParent,
409 /* out */ bool& reauthenticated,
410 /* inout */ bool& isBlankPassword,
411 /* inout */ int64_t& prefLastChanged,
412 /* out */ bool& isAutoAdminLogonEnabled,
413 /* out */ bool& isRequireSignonEnabled) {
414 reauthenticated = false;
415 #if defined(XP_WIN)
416 return ReauthenticateUserWindows(
417 prompt, caption, hwndParent, reauthenticated, isBlankPassword,
418 prefLastChanged, isAutoAdminLogonEnabled, isRequireSignonEnabled);
419 #elif defined(XP_MACOSX)
420 return ReauthenticateUserMacOS(prompt, reauthenticated, isBlankPassword);
421 #else
422 return NS_OK;
423 #endif // Reauthentication is not implemented for this platform.
426 static void BackgroundReauthenticateUser(RefPtr<Promise>& aPromise,
427 const nsAString& aMessageText,
428 const nsAString& aCaptionText,
429 const WindowsHandle& hwndParent,
430 bool isBlankPassword,
431 int64_t prefLastChanged) {
432 nsAutoCString recovery;
433 bool reauthenticated;
434 bool isAutoAdminLogonEnabled;
435 bool isRequireSignonEnabled;
436 nsresult rv = ReauthenticateUser(
437 aMessageText, aCaptionText, hwndParent, reauthenticated, isBlankPassword,
438 prefLastChanged, isAutoAdminLogonEnabled, isRequireSignonEnabled);
440 nsTArray<int32_t> prefLastChangedUpdates;
441 #if defined(XP_WIN)
442 // Increase the lastChanged time to account for clock skew.
443 prefLastChanged += PR_USEC_PER_SEC;
444 // Need to split the 64bit integer to its hi and lo bits before sending it
445 // back to JS.
446 int32_t prefLastChangedHi = prefLastChanged / Int32Modulo;
447 int32_t prefLastChangedLo = prefLastChanged % Int32Modulo;
448 prefLastChangedUpdates.AppendElement(prefLastChangedHi);
449 prefLastChangedUpdates.AppendElement(prefLastChangedLo);
450 #endif
452 nsTArray<int32_t> results;
453 results.AppendElement(reauthenticated);
454 results.AppendElement(isBlankPassword);
455 #if defined(XP_WIN)
456 results.AppendElement(isAutoAdminLogonEnabled);
457 results.AppendElement(isRequireSignonEnabled);
458 #endif
459 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
460 "BackgroundReauthenticateUserResolve",
461 [rv, results = std::move(results),
462 prefLastChangedUpdates = std::move(prefLastChangedUpdates),
463 aPromise = std::move(aPromise)]() {
464 if (NS_FAILED(rv)) {
465 aPromise->MaybeReject(rv);
466 } else {
467 aPromise->MaybeResolve(results);
470 nsresult rv = Preferences::SetBool(PREF_BLANK_PASSWORD, results[1]);
471 if (NS_FAILED(rv)) {
472 return;
474 if (prefLastChangedUpdates.Length() > 1) {
475 rv = Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_HI,
476 prefLastChangedUpdates[0]);
477 if (NS_FAILED(rv)) {
478 return;
480 Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_LO,
481 prefLastChangedUpdates[1]);
483 }));
484 NS_DispatchToMainThread(runnable.forget());
487 NS_IMETHODIMP
488 OSReauthenticator::AsyncReauthenticateUser(const nsAString& aMessageText,
489 const nsAString& aCaptionText,
490 mozIDOMWindow* aParentWindow,
491 JSContext* aCx,
492 Promise** promiseOut) {
493 NS_ENSURE_ARG_POINTER(aCx);
495 RefPtr<Promise> promiseHandle;
496 nsresult rv = GetPromise(aCx, promiseHandle);
497 if (NS_FAILED(rv)) {
498 return rv;
501 WindowsHandle hwndParent = 0;
502 if (aParentWindow) {
503 nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aParentWindow);
504 nsIDocShell* docShell = win->GetDocShell();
505 if (docShell) {
506 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(docShell);
507 if (baseWindow) {
508 nsCOMPtr<nsIWidget> widget;
509 baseWindow->GetMainWidget(getter_AddRefs(widget));
510 if (widget) {
511 hwndParent = reinterpret_cast<WindowsHandle>(
512 widget->GetNativeData(NS_NATIVE_WINDOW));
518 int64_t prefLastChanged = 0;
519 bool isBlankPassword = false;
520 #if defined(XP_WIN)
521 // These preferences are only supported on Windows.
522 // Preferences are read/write main-thread only.
523 int32_t prefLastChangedLo;
524 int32_t prefLastChangedHi;
525 rv = Preferences::GetBool(PREF_BLANK_PASSWORD, &isBlankPassword);
526 if (NS_FAILED(rv)) {
527 return rv;
529 rv = Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_LO, &prefLastChangedLo);
530 if (NS_FAILED(rv)) {
531 return rv;
533 rv = Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_HI, &prefLastChangedHi);
534 if (NS_FAILED(rv)) {
535 return rv;
537 prefLastChanged = prefLastChangedHi * Int32Modulo + prefLastChangedLo;
538 #endif
540 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
541 "BackgroundReauthenticateUser",
542 [promiseHandle, aMessageText = nsAutoString(aMessageText),
543 aCaptionText = nsAutoString(aCaptionText), hwndParent, isBlankPassword,
544 prefLastChanged]() mutable {
545 BackgroundReauthenticateUser(promiseHandle, aMessageText, aCaptionText,
546 hwndParent, isBlankPassword,
547 prefLastChanged);
548 }));
550 nsCOMPtr<nsIEventTarget> target(
551 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
552 if (!target) {
553 return NS_ERROR_FAILURE;
555 rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
556 if (NS_WARN_IF(NS_FAILED(rv))) {
557 return rv;
560 promiseHandle.forget(promiseOut);
561 return NS_OK;