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"
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
;
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"
42 # include <combaseapi.h>
43 # include <ntsecapi.h>
46 # include "nsIWindowsRegKey.h" // Must be included after <windows.h> for HKEY definition
47 # define SECURITY_WIN32
48 # include <security.h>
50 # if !defined(__MINGW32__)
52 # undef ACCESS_READ // nsWindowsRegKey defines its own ACCESS_READ
53 # endif // !defined(__MINGW32__)
55 typedef HANDLE pointer
;
56 void operator()(HANDLE h
) {
57 if (h
!= INVALID_HANDLE_VALUE
) {
63 typedef LPVOID pointer
;
65 explicit BufferFreer(ULONG size
) : mSize(size
) {}
66 void operator()(LPVOID b
) {
67 SecureZeroMemory(b
, mSize
);
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
) {
88 // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
89 mozilla::Unused
<< GetTokenInformation(token
.get(), TokenUser
, nullptr, 0,
91 if (!length
|| GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
92 MOZ_LOG(gCredentialManagerSecretLog
, LogLevel::Debug
,
93 ("Unable to obtain current token info."));
96 std::unique_ptr
<char[]> token_info(new char[length
]);
97 if (!GetTokenInformation(token
.get(), TokenUser
, token_info
.get(), length
,
99 MOZ_LOG(gCredentialManagerSecretLog
, LogLevel::Debug
,
100 ("Unable to obtain current token info (second call, possible "
107 std::unique_ptr
<char[]> GetUserTokenInfo() {
108 // Get current user sid to make sure the same user got logged in.
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."));
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();
125 LPUSER_INFO_1 user_info
= NULL
;
126 DWORD passwordAgeInSeconds
= 0;
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
);
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
);
145 bool IsAutoAdminLogonEnabled() {
146 // https://support.microsoft.com/en-us/help/324737/how-to-turn-on-automatic-logon-in-windows
148 nsCOMPtr
<nsIWindowsRegKey
> regKey
=
149 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv
);
155 nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE
,
157 u
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
158 nsIWindowsRegKey::ACCESS_READ
);
164 rv
= regKey
->ReadStringValue(u
"AutoAdminLogon"_ns
, value
);
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
176 nsCOMPtr
<nsIWindowsRegKey
> regKey
=
177 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv
);
182 rv
= regKey
->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE
,
183 u
"System\\CurrentControlSet\\Control\\Power\\User\\Power"
185 nsIWindowsRegKey::ACCESS_READ
);
190 nsAutoString activePowerScheme
;
191 rv
= regKey
->ReadStringValue(u
"ActivePowerScheme"_ns
, activePowerScheme
);
197 rv
= regKey
->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE
,
198 u
"System\\CurrentControlSet\\Control\\Power\\User\\Power"
201 u
"\\0e796bdb-100d-47d6-a2d5-f7d2daa51f51"_ns
,
202 nsIWindowsRegKey::ACCESS_READ
);
208 rv
= regKey
->ReadIntValue(u
"ACSettingIndex"_ns
, &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
'\\');
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
;
258 LogonUser(usernameNoDomain
, L
".", L
"", LOGON32_LOGON_INTERACTIVE
,
259 LOGON32_PROVIDER_DEFAULT
, &logonUserHandle
);
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
) {
277 isBlankPassword
= false;
280 // Update any preferences, assuming domain members do not have blank
282 isBlankPassword
= false;
285 isAutoAdminLogonEnabled
= IsAutoAdminLogonEnabled();
287 isRequireSignonEnabled
= IsRequireSignonEnabled();
289 // Is used in next iteration if the previous login failed.
291 std::unique_ptr
<char[]> userTokenInfo
= GetUserTokenInfo();
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."));
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.
343 PCHAR contextName
= const_cast<PCHAR
>("Mozilla");
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
;
356 void* profileBuffer
= nullptr;
357 ULONG profileBufferLength
= 0;
358 QUOTA_LIMITS limits
= {0};
360 HANDLE token
= INVALID_HANDLE_VALUE
;
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."));
376 err
= LsaNtStatusToWinError(sts
);
377 MOZ_LOG(gCredentialManagerSecretLog
, LogLevel::Debug
,
378 ("Login failed with %lx (%lx).", sts
, err
));
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
;
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;
399 err
= ERROR_LOGON_FAILURE
;
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;
416 return ReauthenticateUserWindows(
417 prompt
, caption
, hwndParent
, reauthenticated
, isBlankPassword
,
418 prefLastChanged
, isAutoAdminLogonEnabled
, isRequireSignonEnabled
);
419 #elif defined(XP_MACOSX)
420 return ReauthenticateUserMacOS(prompt
, reauthenticated
, isBlankPassword
);
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
;
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
446 int32_t prefLastChangedHi
= prefLastChanged
/ Int32Modulo
;
447 int32_t prefLastChangedLo
= prefLastChanged
% Int32Modulo
;
448 prefLastChangedUpdates
.AppendElement(prefLastChangedHi
);
449 prefLastChangedUpdates
.AppendElement(prefLastChangedLo
);
452 nsTArray
<int32_t> results
;
453 results
.AppendElement(reauthenticated
);
454 results
.AppendElement(isBlankPassword
);
456 results
.AppendElement(isAutoAdminLogonEnabled
);
457 results
.AppendElement(isRequireSignonEnabled
);
459 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
460 "BackgroundReauthenticateUserResolve",
461 [rv
, results
= std::move(results
),
462 prefLastChangedUpdates
= std::move(prefLastChangedUpdates
),
463 aPromise
= std::move(aPromise
)]() {
465 aPromise
->MaybeReject(rv
);
467 aPromise
->MaybeResolve(results
);
470 nsresult rv
= Preferences::SetBool(PREF_BLANK_PASSWORD
, results
[1]);
474 if (prefLastChangedUpdates
.Length() > 1) {
475 rv
= Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_HI
,
476 prefLastChangedUpdates
[0]);
480 Preferences::SetInt(PREF_PASSWORD_LAST_CHANGED_LO
,
481 prefLastChangedUpdates
[1]);
484 NS_DispatchToMainThread(runnable
.forget());
488 OSReauthenticator::AsyncReauthenticateUser(const nsAString
& aMessageText
,
489 const nsAString
& aCaptionText
,
490 mozIDOMWindow
* aParentWindow
,
492 Promise
** promiseOut
) {
493 NS_ENSURE_ARG_POINTER(aCx
);
495 RefPtr
<Promise
> promiseHandle
;
496 nsresult rv
= GetPromise(aCx
, promiseHandle
);
501 WindowsHandle hwndParent
= 0;
503 nsPIDOMWindowInner
* win
= nsPIDOMWindowInner::From(aParentWindow
);
504 nsIDocShell
* docShell
= win
->GetDocShell();
506 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(docShell
);
508 nsCOMPtr
<nsIWidget
> widget
;
509 baseWindow
->GetMainWidget(getter_AddRefs(widget
));
511 hwndParent
= reinterpret_cast<WindowsHandle
>(
512 widget
->GetNativeData(NS_NATIVE_WINDOW
));
518 int64_t prefLastChanged
= 0;
519 bool isBlankPassword
= false;
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
);
529 rv
= Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_LO
, &prefLastChangedLo
);
533 rv
= Preferences::GetInt(PREF_PASSWORD_LAST_CHANGED_HI
, &prefLastChangedHi
);
537 prefLastChanged
= prefLastChangedHi
* Int32Modulo
+ prefLastChangedLo
;
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
,
550 nsCOMPtr
<nsIEventTarget
> target(
551 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
));
553 return NS_ERROR_FAILURE
;
555 rv
= target
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
556 if (NS_WARN_IF(NS_FAILED(rv
))) {
560 promiseHandle
.forget(promiseOut
);