Bug 1498518 - user authentication prompts for Windows, r=keeler,mhowell
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Tue, 23 Oct 2018 09:05:07 +0000
changeset 490882 c29048279fcc7ffa7785a17b3afaf7b97e915c72
parent 490881 72ce1b22eee8336b6c1b60221696a1e01cc2dc39
child 490883 99748ccfba864bee288669cac6d5fe7fb09c181f
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerskeeler, mhowell
bugs1498518
milestone65.0a1
Bug 1498518 - user authentication prompts for Windows, r=keeler,mhowell Depends on D8480 Differential Revision: https://phabricator.services.mozilla.com/D8530
security/manager/ssl/OSReauthenticator.cpp
security/manager/ssl/moz.build
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/manager/ssl/OSReauthenticator.cpp
+++ b/security/manager/ssl/OSReauthenticator.cpp
@@ -8,20 +8,216 @@
 
 #include "OSKeyStore.h"
 
 NS_IMPL_ISUPPORTS(OSReauthenticator, nsIOSReauthenticator)
 
 using namespace mozilla;
 using dom::Promise;
 
+#if defined(XP_WIN)
+#include <combaseapi.h>
+#include <ntsecapi.h>
+#include <wincred.h>
+#include <windows.h>
+struct HandleCloser
+{
+  typedef HANDLE pointer;
+  void operator()(HANDLE h)
+  {
+    if(h != INVALID_HANDLE_VALUE) {
+        CloseHandle(h);
+    }
+  }
+};
+struct BufferFreer
+{
+    typedef LPVOID pointer;
+    void operator()(LPVOID b)
+    {
+      CoTaskMemFree(b);
+    }
+};
+typedef std::unique_ptr<HANDLE, HandleCloser> ScopedHANDLE;
+typedef std::unique_ptr<LPVOID, BufferFreer> ScopedBuffer;
+
+// Get the token info holding the sid.
+std::unique_ptr<char[]>
+GetTokenInfo(ScopedHANDLE& token)
+{
+  DWORD length = 0;
+  // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
+  mozilla::Unused << GetTokenInformation(token.get(), TokenUser, nullptr, 0,
+                                         &length);
+  if (!length || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Unable to obtain current token info."));
+    return nullptr;
+  }
+  std::unique_ptr<char[]> token_info(new char[length]);
+  if (!GetTokenInformation(token.get(), TokenUser, token_info.get(), length,
+                           &length)) {
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Unable to obtain current token info (second call, possible system error."));
+    return nullptr;
+  }
+  return token_info;
+}
+
+std::unique_ptr<char[]>
+GetUserTokenInfo()
+{
+  // Get current user sid to make sure the same user got logged in.
+  HANDLE token;
+  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+    // Couldn't get a process token. This will fail any unlock attempts later.
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Unable to obtain process token."));
+    return nullptr;
+  }
+  ScopedHANDLE scopedToken(token);
+  return GetTokenInfo(scopedToken);
+}
+
+// Use the Windows credential prompt to ask the user to authenticate the
+// currently used account.
+static nsresult
+ReauthenticateUserWindows(const nsACString& aPrompt,
+                          /* out */ bool& reauthenticated)
+{
+  reauthenticated = false;
+
+  HANDLE lsa;
+  // Get authentication handle for future user authentications.
+  // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted
+  if (LsaConnectUntrusted(&lsa) != ERROR_SUCCESS) {
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Error aquiring lsa. Authentication attempts will fail."));
+    return NS_ERROR_FAILURE;
+  }
+  ScopedHANDLE scopedLsa(lsa);
+
+  std::unique_ptr<char[]> userTokenInfo = GetUserTokenInfo();
+  if (!userTokenInfo || lsa == INVALID_HANDLE_VALUE) {
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Error setting up login and user token."));
+    return NS_ERROR_FAILURE;
+  }
+
+  // CredUI prompt.
+  CREDUI_INFOW credui = {};
+  credui.cbSize = sizeof(credui);
+  // TODO: maybe set parent (Firefox) here.
+  credui.hwndParent = nullptr;
+  const nsString& prompt = PromiseFlatString(NS_ConvertUTF8toUTF16(aPrompt));
+  credui.pszMessageText = prompt.get();
+  credui.pszCaptionText = nullptr;
+  credui.hbmBanner = nullptr; // ignored
+
+  ULONG authPackage = 0;
+  LPVOID outCredBuffer = nullptr;
+  ULONG outCredSize = 0;
+  BOOL save = false;
+  // Could be used in next iteration if the previous login failed.
+  DWORD err = 0;
+
+  // Get user's Windows credentials.
+  // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creduipromptforwindowscredentialsw
+  err = CredUIPromptForWindowsCredentialsW(&credui, err, &authPackage,
+                          nullptr, 0, &outCredBuffer, &outCredSize, &save,
+                          CREDUIWIN_ENUMERATE_CURRENT_USER);
+  ScopedBuffer scopedOutCredBuffer(outCredBuffer);
+  if (err != ERROR_SUCCESS) {
+    MOZ_LOG(gCredentialManagerSecretLog,
+            LogLevel::Debug,
+            ("Error getting authPackage for user login"));
+    return NS_ERROR_FAILURE;
+  }
+
+  // Verify the credentials.
+  TOKEN_SOURCE source;
+  PCHAR contextName = const_cast<PCHAR>("Mozilla");
+  size_t nameLength = std::min(TOKEN_SOURCE_LENGTH,
+                               static_cast<int>(strlen(contextName)));
+  // Note that the string must not be longer than TOKEN_SOURCE_LENGTH.
+  memcpy(source.SourceName, contextName, nameLength);
+  // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-allocatelocallyuniqueid
+  if (!AllocateLocallyUniqueId(&source.SourceIdentifier)) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Error allocating ID for logon process."));
+      return NS_ERROR_FAILURE;
+  }
+
+  NTSTATUS substs;
+  void* profileBuffer = nullptr;
+  ULONG profileBufferLength = 0;
+  QUOTA_LIMITS limits = {0};
+  LUID luid;
+  HANDLE token;
+  LSA_STRING name;
+  name.Buffer = contextName;
+  name.Length = strlen(name.Buffer);
+  name.MaximumLength = name.Length;
+  // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsalogonuser
+  NTSTATUS sts = LsaLogonUser(scopedLsa.get(), &name, (SECURITY_LOGON_TYPE)Interactive,
+                      authPackage, scopedOutCredBuffer.get(),
+                      outCredSize, nullptr, &source, &profileBuffer,
+                      &profileBufferLength, &luid, &token, &limits,
+                      &substs);
+  ScopedHANDLE scopedToken(token);
+  LsaFreeReturnBuffer(profileBuffer);
+  LsaDeregisterLogonProcess(scopedLsa.get());
+  if (sts == ERROR_SUCCESS) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("User logged in successfully."));
+  } else {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Login failed with %lx (%lx).", sts, LsaNtStatusToWinError(sts)));
+      return NS_ERROR_FAILURE;
+  }
+
+  // The user can select any user to log-in on the authentication prompt.
+  // Make sure that the logged in user is the current user.
+  std::unique_ptr<char[]> logonTokenInfo = GetTokenInfo(scopedToken);
+  if (!logonTokenInfo) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Error getting logon token info."));
+      return NS_ERROR_FAILURE;
+  }
+  PSID logonSID = reinterpret_cast<TOKEN_USER*>(logonTokenInfo.get())->User.Sid;
+  PSID userSID = reinterpret_cast<TOKEN_USER*>(userTokenInfo.get())->User.Sid;
+  if (EqualSid(userSID, logonSID)) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Login successfully (correct user)."));
+      reauthenticated = true;
+      return NS_OK;
+  }
+  MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
+          ("Login failed (wrong user)."));
+  return NS_ERROR_FAILURE;
+}
+#endif
+
 static nsresult
 ReauthenticateUser(const nsACString& prompt, /* out */ bool& reauthenticated)
 {
   reauthenticated = false;
+#if defined(XP_WIN)
+  return ReauthenticateUserWindows(prompt, reauthenticated);
+#endif // Reauthentication is not implemented for this platform.
   return NS_OK;
 }
 
 static void
 BackgroundReauthenticateUser(RefPtr<Promise>& aPromise,
                              const nsACString& aPrompt)
 {
   nsAutoCString recovery;
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -150,16 +150,19 @@ if CONFIG['OS_ARCH'] == 'Darwin':
     UNIFIED_SOURCES += [
         'KeychainSecret.cpp',
     ]
     OS_LIBS += [
         '-framework Security'
     ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
+    OS_LIBS += [
+        'credui'
+    ]
     UNIFIED_SOURCES += [
         'CredentialManagerSecret.cpp',
     ]
 
 IPDL_SOURCES += [
     'PPSMContentDownloader.ipdl',
 ]
 
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -137,16 +137,19 @@ run-sequentially = hardcoded ports
 [test_ocsp_stapling_with_intermediate.js]
 run-sequentially = hardcoded ports
 [test_ocsp_timeout.js]
 run-sequentially = hardcoded ports
 [test_ocsp_url.js]
 run-sequentially = hardcoded ports
 [test_oskeystore.js]
 [test_osreauthenticator.js]
+# Reauthentication has been implemented on Windows, so running this test results in
+# the OS popping up a dialog, which means we can't run it in automation.
+skip-if = os == 'win'
 [test_password_prompt.js]
 [test_pinning.js]
 run-sequentially = hardcoded ports
 # This test can take longer than 300 seconds on B2G emulator debug builds, so
 # give it enough time to finish. See bug 1081128.
 requesttimeoutfactor = 2
 [test_pinning_dynamic.js]
 [test_pinning_header_parsing.js]