mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-31 06:35:12 +00:00 
			
		
		
		
	| @@ -49,32 +49,38 @@ class TestCookies(unittest.TestCase): | ||||
|         """ based on https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base/nix/xdg_util_unittest.cc """ | ||||
|         test_cases = [ | ||||
|             ({}, _LinuxDesktopEnvironment.OTHER), | ||||
|             ({'DESKTOP_SESSION': 'my_custom_de'}, _LinuxDesktopEnvironment.OTHER), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'my_custom_de'}, _LinuxDesktopEnvironment.OTHER), | ||||
| 
 | ||||
|             ({'DESKTOP_SESSION': 'gnome'}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'DESKTOP_SESSION': 'mate'}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'DESKTOP_SESSION': 'kde4'}, _LinuxDesktopEnvironment.KDE), | ||||
|             ({'DESKTOP_SESSION': 'kde'}, _LinuxDesktopEnvironment.KDE), | ||||
|             ({'DESKTOP_SESSION': 'kde4'}, _LinuxDesktopEnvironment.KDE4), | ||||
|             ({'DESKTOP_SESSION': 'kde'}, _LinuxDesktopEnvironment.KDE3), | ||||
|             ({'DESKTOP_SESSION': 'xfce'}, _LinuxDesktopEnvironment.XFCE), | ||||
| 
 | ||||
|             ({'GNOME_DESKTOP_SESSION_ID': 1}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'KDE_FULL_SESSION': 1}, _LinuxDesktopEnvironment.KDE), | ||||
|             ({'KDE_FULL_SESSION': 1}, _LinuxDesktopEnvironment.KDE3), | ||||
|             ({'KDE_FULL_SESSION': 1, 'DESKTOP_SESSION': 'kde4'}, _LinuxDesktopEnvironment.KDE4), | ||||
| 
 | ||||
|             ({'XDG_CURRENT_DESKTOP': 'X-Cinnamon'}, _LinuxDesktopEnvironment.CINNAMON), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Deepin'}, _LinuxDesktopEnvironment.DEEPIN), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'GNOME'}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'GNOME:GNOME-Classic'}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'GNOME : GNOME-Classic'}, _LinuxDesktopEnvironment.GNOME), | ||||
| 
 | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Unity', 'DESKTOP_SESSION': 'gnome-fallback'}, _LinuxDesktopEnvironment.GNOME), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'KDE', 'KDE_SESSION_VERSION': '5'}, _LinuxDesktopEnvironment.KDE), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'KDE'}, _LinuxDesktopEnvironment.KDE), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'KDE', 'KDE_SESSION_VERSION': '5'}, _LinuxDesktopEnvironment.KDE5), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'KDE', 'KDE_SESSION_VERSION': '6'}, _LinuxDesktopEnvironment.KDE6), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'KDE'}, _LinuxDesktopEnvironment.KDE4), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Pantheon'}, _LinuxDesktopEnvironment.PANTHEON), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'UKUI'}, _LinuxDesktopEnvironment.UKUI), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Unity'}, _LinuxDesktopEnvironment.UNITY), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Unity:Unity7'}, _LinuxDesktopEnvironment.UNITY), | ||||
|             ({'XDG_CURRENT_DESKTOP': 'Unity:Unity8'}, _LinuxDesktopEnvironment.UNITY), | ||||
|         ] | ||||
| 
 | ||||
|         for env, expected_desktop_environment in test_cases: | ||||
|             self.assertEqual(_get_linux_desktop_environment(env), expected_desktop_environment) | ||||
|             self.assertEqual(_get_linux_desktop_environment(env, Logger()), expected_desktop_environment) | ||||
| 
 | ||||
|     def test_chrome_cookie_decryptor_linux_derive_key(self): | ||||
|         key = LinuxChromeCookieDecryptor.derive_key(b'abc') | ||||
|   | ||||
| @@ -353,7 +353,9 @@ class ChromeCookieDecryptor: | ||||
|         Linux: | ||||
|         - cookies are either v10 or v11 | ||||
|             - v10: AES-CBC encrypted with a fixed key | ||||
|                 - also attempts empty password if decryption fails | ||||
|             - v11: AES-CBC encrypted with an OS protected key (keyring) | ||||
|                 - also attempts empty password if decryption fails | ||||
|             - v11 keys can be stored in various places depending on the activate desktop environment [2] | ||||
| 
 | ||||
|         Mac: | ||||
| @@ -368,7 +370,7 @@ class ChromeCookieDecryptor: | ||||
| 
 | ||||
|     Sources: | ||||
|     - [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/ | ||||
|     - [2] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_linux.cc | ||||
|     - [2] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_linux.cc | ||||
|         - KeyStorageLinux::CreateService | ||||
|     """ | ||||
| 
 | ||||
| @@ -390,6 +392,7 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|     def __init__(self, browser_keyring_name, logger, *, keyring=None): | ||||
|         self._logger = logger | ||||
|         self._v10_key = self.derive_key(b'peanuts') | ||||
|         self._empty_key = self.derive_key(b'') | ||||
|         self._cookie_counts = {'v10': 0, 'v11': 0, 'other': 0} | ||||
|         self._browser_keyring_name = browser_keyring_name | ||||
|         self._keyring = keyring | ||||
| @@ -402,25 +405,36 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|     @staticmethod | ||||
|     def derive_key(password): | ||||
|         # values from | ||||
|         # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_linux.cc | ||||
|         # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_linux.cc | ||||
|         return pbkdf2_sha1(password, salt=b'saltysalt', iterations=1, key_length=16) | ||||
| 
 | ||||
|     def decrypt(self, encrypted_value): | ||||
|         """ | ||||
| 
 | ||||
|         following the same approach as the fix in [1]: if cookies fail to decrypt then attempt to decrypt | ||||
|         with an empty password. The failure detection is not the same as what chromium uses so the | ||||
|         results won't be perfect | ||||
| 
 | ||||
|         References: | ||||
|             - [1] https://chromium.googlesource.com/chromium/src/+/bbd54702284caca1f92d656fdcadf2ccca6f4165%5E%21/ | ||||
|                 - a bugfix to try an empty password as a fallback | ||||
|         """ | ||||
|         version = encrypted_value[:3] | ||||
|         ciphertext = encrypted_value[3:] | ||||
| 
 | ||||
|         if version == b'v10': | ||||
|             self._cookie_counts['v10'] += 1 | ||||
|             return _decrypt_aes_cbc(ciphertext, self._v10_key, self._logger) | ||||
|             return _decrypt_aes_cbc_multi(ciphertext, (self._v10_key, self._empty_key), self._logger) | ||||
| 
 | ||||
|         elif version == b'v11': | ||||
|             self._cookie_counts['v11'] += 1 | ||||
|             if self._v11_key is None: | ||||
|                 self._logger.warning('cannot decrypt v11 cookies: no key found', only_once=True) | ||||
|                 return None | ||||
|             return _decrypt_aes_cbc(ciphertext, self._v11_key, self._logger) | ||||
|             return _decrypt_aes_cbc_multi(ciphertext, (self._v11_key, self._empty_key), self._logger) | ||||
| 
 | ||||
|         else: | ||||
|             self._logger.warning(f'unknown cookie version: "{version}"', only_once=True) | ||||
|             self._cookie_counts['other'] += 1 | ||||
|             return None | ||||
| 
 | ||||
| @@ -435,7 +449,7 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|     @staticmethod | ||||
|     def derive_key(password): | ||||
|         # values from | ||||
|         # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_mac.mm | ||||
|         # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_mac.mm | ||||
|         return pbkdf2_sha1(password, salt=b'saltysalt', iterations=1003, key_length=16) | ||||
| 
 | ||||
|     def decrypt(self, encrypted_value): | ||||
| @@ -448,12 +462,12 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|                 self._logger.warning('cannot decrypt v10 cookies: no key found', only_once=True) | ||||
|                 return None | ||||
| 
 | ||||
|             return _decrypt_aes_cbc(ciphertext, self._v10_key, self._logger) | ||||
|             return _decrypt_aes_cbc_multi(ciphertext, (self._v10_key,), self._logger) | ||||
| 
 | ||||
|         else: | ||||
|             self._cookie_counts['other'] += 1 | ||||
|             # other prefixes are considered 'old data' which were stored as plaintext | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_mac.mm | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_mac.mm | ||||
|             return encrypted_value | ||||
| 
 | ||||
| 
 | ||||
| @@ -473,7 +487,7 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|                 self._logger.warning('cannot decrypt v10 cookies: no key found', only_once=True) | ||||
|                 return None | ||||
| 
 | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_win.cc | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc | ||||
|             #   kNonceLength | ||||
|             nonce_length = 96 // 8 | ||||
|             # boringssl | ||||
| @@ -490,7 +504,7 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor): | ||||
|         else: | ||||
|             self._cookie_counts['other'] += 1 | ||||
|             # any other prefix means the data is DPAPI encrypted | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_win.cc | ||||
|             # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc | ||||
|             return _decrypt_windows_dpapi(encrypted_value, self._logger).decode() | ||||
| 
 | ||||
| 
 | ||||
| @@ -673,27 +687,35 @@ class _LinuxDesktopEnvironment(Enum): | ||||
|     """ | ||||
|     OTHER = auto() | ||||
|     CINNAMON = auto() | ||||
|     DEEPIN = auto() | ||||
|     GNOME = auto() | ||||
|     KDE = auto() | ||||
|     KDE3 = auto() | ||||
|     KDE4 = auto() | ||||
|     KDE5 = auto() | ||||
|     KDE6 = auto() | ||||
|     PANTHEON = auto() | ||||
|     UKUI = auto() | ||||
|     UNITY = auto() | ||||
|     XFCE = auto() | ||||
|     LXQT = auto() | ||||
| 
 | ||||
| 
 | ||||
| class _LinuxKeyring(Enum): | ||||
|     """ | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_util_linux.h | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_util_linux.h | ||||
|     SelectedLinuxBackend | ||||
|     """ | ||||
|     KWALLET = auto() | ||||
|     GNOMEKEYRING = auto() | ||||
|     BASICTEXT = auto() | ||||
|     KWALLET4 = auto()  # this value is just called KWALLET in the chromium source but it is for KDE4 only | ||||
|     KWALLET5 = auto() | ||||
|     KWALLET6 = auto() | ||||
|     GNOME_KEYRING = auto() | ||||
|     BASIC_TEXT = auto() | ||||
| 
 | ||||
| 
 | ||||
| SUPPORTED_KEYRINGS = _LinuxKeyring.__members__.keys() | ||||
| 
 | ||||
| 
 | ||||
| def _get_linux_desktop_environment(env): | ||||
| def _get_linux_desktop_environment(env, logger): | ||||
|     """ | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base/nix/xdg_util.cc | ||||
|     GetDesktopEnvironment | ||||
| @@ -708,51 +730,97 @@ def _get_linux_desktop_environment(env): | ||||
|                 return _LinuxDesktopEnvironment.GNOME | ||||
|             else: | ||||
|                 return _LinuxDesktopEnvironment.UNITY | ||||
|         elif xdg_current_desktop == 'Deepin': | ||||
|             return _LinuxDesktopEnvironment.DEEPIN | ||||
|         elif xdg_current_desktop == 'GNOME': | ||||
|             return _LinuxDesktopEnvironment.GNOME | ||||
|         elif xdg_current_desktop == 'X-Cinnamon': | ||||
|             return _LinuxDesktopEnvironment.CINNAMON | ||||
|         elif xdg_current_desktop == 'KDE': | ||||
|             return _LinuxDesktopEnvironment.KDE | ||||
|             kde_version = env.get('KDE_SESSION_VERSION', None) | ||||
|             if kde_version == '5': | ||||
|                 return _LinuxDesktopEnvironment.KDE5 | ||||
|             elif kde_version == '6': | ||||
|                 return _LinuxDesktopEnvironment.KDE6 | ||||
|             elif kde_version == '4': | ||||
|                 return _LinuxDesktopEnvironment.KDE4 | ||||
|             else: | ||||
|                 logger.info(f'unknown KDE version: "{kde_version}". Assuming KDE4') | ||||
|                 return _LinuxDesktopEnvironment.KDE4 | ||||
|         elif xdg_current_desktop == 'Pantheon': | ||||
|             return _LinuxDesktopEnvironment.PANTHEON | ||||
|         elif xdg_current_desktop == 'XFCE': | ||||
|             return _LinuxDesktopEnvironment.XFCE | ||||
|         elif xdg_current_desktop == 'UKUI': | ||||
|             return _LinuxDesktopEnvironment.UKUI | ||||
|         elif xdg_current_desktop == 'LXQt': | ||||
|             return _LinuxDesktopEnvironment.LXQT | ||||
|         else: | ||||
|             logger.info(f'XDG_CURRENT_DESKTOP is set to an unknown value: "{xdg_current_desktop}"') | ||||
| 
 | ||||
|     elif desktop_session is not None: | ||||
|         if desktop_session in ('mate', 'gnome'): | ||||
|         if desktop_session == 'deepin': | ||||
|             return _LinuxDesktopEnvironment.DEEPIN | ||||
|         elif desktop_session in ('mate', 'gnome'): | ||||
|             return _LinuxDesktopEnvironment.GNOME | ||||
|         elif 'kde' in desktop_session: | ||||
|             return _LinuxDesktopEnvironment.KDE | ||||
|         elif 'xfce' in desktop_session: | ||||
|         elif desktop_session in ('kde4', 'kde-plasma'): | ||||
|             return _LinuxDesktopEnvironment.KDE4 | ||||
|         elif desktop_session == 'kde': | ||||
|             if 'KDE_SESSION_VERSION' in env: | ||||
|                 return _LinuxDesktopEnvironment.KDE4 | ||||
|             else: | ||||
|                 return _LinuxDesktopEnvironment.KDE3 | ||||
|         elif 'xfce' in desktop_session or desktop_session == 'xubuntu': | ||||
|             return _LinuxDesktopEnvironment.XFCE | ||||
|         elif desktop_session == 'ukui': | ||||
|             return _LinuxDesktopEnvironment.UKUI | ||||
|         else: | ||||
|             logger.info(f'DESKTOP_SESSION is set to an unknown value: "{desktop_session}"') | ||||
| 
 | ||||
|     else: | ||||
|         if 'GNOME_DESKTOP_SESSION_ID' in env: | ||||
|             return _LinuxDesktopEnvironment.GNOME | ||||
|         elif 'KDE_FULL_SESSION' in env: | ||||
|             return _LinuxDesktopEnvironment.KDE | ||||
|             if 'KDE_SESSION_VERSION' in env: | ||||
|                 return _LinuxDesktopEnvironment.KDE4 | ||||
|             else: | ||||
|                 return _LinuxDesktopEnvironment.KDE3 | ||||
|     return _LinuxDesktopEnvironment.OTHER | ||||
| 
 | ||||
| 
 | ||||
| def _choose_linux_keyring(logger): | ||||
|     """ | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_util_linux.cc | ||||
|     SelectBackend | ||||
|     SelectBackend in [1] | ||||
| 
 | ||||
|     There is currently support for forcing chromium to use BASIC_TEXT by creating a file called | ||||
|     `Disable Local Encryption` [1] in the user data dir. The function to write this file (`WriteBackendUse()` [1]) | ||||
|     does not appear to be called anywhere other than in tests, so the user would have to create this file manually | ||||
|     and so would be aware enough to tell yt-dlp to use the BASIC_TEXT keyring. | ||||
| 
 | ||||
|     References: | ||||
|         - [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_util_linux.cc | ||||
|     """ | ||||
|     desktop_environment = _get_linux_desktop_environment(os.environ) | ||||
|     desktop_environment = _get_linux_desktop_environment(os.environ, logger) | ||||
|     logger.debug(f'detected desktop environment: {desktop_environment.name}') | ||||
|     if desktop_environment == _LinuxDesktopEnvironment.KDE: | ||||
|         linux_keyring = _LinuxKeyring.KWALLET | ||||
|     elif desktop_environment == _LinuxDesktopEnvironment.OTHER: | ||||
|         linux_keyring = _LinuxKeyring.BASICTEXT | ||||
|     if desktop_environment == _LinuxDesktopEnvironment.KDE4: | ||||
|         linux_keyring = _LinuxKeyring.KWALLET4 | ||||
|     elif desktop_environment == _LinuxDesktopEnvironment.KDE5: | ||||
|         linux_keyring = _LinuxKeyring.KWALLET5 | ||||
|     elif desktop_environment == _LinuxDesktopEnvironment.KDE6: | ||||
|         linux_keyring = _LinuxKeyring.KWALLET6 | ||||
|     elif desktop_environment in ( | ||||
|         _LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER | ||||
|     ): | ||||
|         linux_keyring = _LinuxKeyring.BASIC_TEXT | ||||
|     else: | ||||
|         linux_keyring = _LinuxKeyring.GNOMEKEYRING | ||||
|         linux_keyring = _LinuxKeyring.GNOME_KEYRING | ||||
|     return linux_keyring | ||||
| 
 | ||||
| 
 | ||||
| def _get_kwallet_network_wallet(logger): | ||||
| def _get_kwallet_network_wallet(keyring, logger): | ||||
|     """ The name of the wallet used to store network passwords. | ||||
| 
 | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/kwallet_dbus.cc | ||||
|     https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/kwallet_dbus.cc | ||||
|     KWalletDBus::NetworkWallet | ||||
|     which does a dbus call to the following function: | ||||
|     https://api.kde.org/frameworks/kwallet/html/classKWallet_1_1Wallet.html | ||||
| @@ -760,10 +828,22 @@ def _get_kwallet_network_wallet(logger): | ||||
|     """ | ||||
|     default_wallet = 'kdewallet' | ||||
|     try: | ||||
|         if keyring == _LinuxKeyring.KWALLET4: | ||||
|             service_name = 'org.kde.kwalletd' | ||||
|             wallet_path = '/modules/kwalletd' | ||||
|         elif keyring == _LinuxKeyring.KWALLET5: | ||||
|             service_name = 'org.kde.kwalletd5' | ||||
|             wallet_path = '/modules/kwalletd5' | ||||
|         elif keyring == _LinuxKeyring.KWALLET6: | ||||
|             service_name = 'org.kde.kwalletd6' | ||||
|             wallet_path = '/modules/kwalletd6' | ||||
|         else: | ||||
|             raise ValueError(keyring) | ||||
| 
 | ||||
|         stdout, _, returncode = Popen.run([ | ||||
|             'dbus-send', '--session', '--print-reply=literal', | ||||
|             '--dest=org.kde.kwalletd5', | ||||
|             '/modules/kwalletd5', | ||||
|             f'--dest={service_name}', | ||||
|             wallet_path, | ||||
|             'org.kde.KWallet.networkWallet' | ||||
|         ], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) | ||||
| 
 | ||||
| @@ -778,8 +858,8 @@ def _get_kwallet_network_wallet(logger): | ||||
|         return default_wallet | ||||
| 
 | ||||
| 
 | ||||
| def _get_kwallet_password(browser_keyring_name, logger): | ||||
|     logger.debug('using kwallet-query to obtain password from kwallet') | ||||
| def _get_kwallet_password(browser_keyring_name, keyring, logger): | ||||
|     logger.debug(f'using kwallet-query to obtain password from {keyring.name}') | ||||
| 
 | ||||
|     if shutil.which('kwallet-query') is None: | ||||
|         logger.error('kwallet-query command not found. KWallet and kwallet-query ' | ||||
| @@ -787,7 +867,7 @@ def _get_kwallet_password(browser_keyring_name, logger): | ||||
|                      'included in the kwallet package for your distribution') | ||||
|         return b'' | ||||
| 
 | ||||
|     network_wallet = _get_kwallet_network_wallet(logger) | ||||
|     network_wallet = _get_kwallet_network_wallet(keyring, logger) | ||||
| 
 | ||||
|     try: | ||||
|         stdout, _, returncode = Popen.run([ | ||||
| @@ -809,8 +889,9 @@ def _get_kwallet_password(browser_keyring_name, logger): | ||||
|                 # checks hasEntry. To verify this: | ||||
|                 # dbus-monitor "interface='org.kde.KWallet'" "type=method_return" | ||||
|                 # while starting chrome. | ||||
|                 # this may be a bug as the intended behaviour is to generate a random password and store | ||||
|                 # it, but that doesn't matter here. | ||||
|                 # this was identified as a bug later and fixed in | ||||
|                 # https://chromium.googlesource.com/chromium/src/+/bbd54702284caca1f92d656fdcadf2ccca6f4165%5E%21/#F0 | ||||
|                 # https://chromium.googlesource.com/chromium/src/+/5463af3c39d7f5b6d11db7fbd51e38cc1974d764 | ||||
|                 return b'' | ||||
|             else: | ||||
|                 logger.debug('password found') | ||||
| @@ -848,11 +929,11 @@ def _get_linux_keyring_password(browser_keyring_name, keyring, logger): | ||||
|     keyring = _LinuxKeyring[keyring] if keyring else _choose_linux_keyring(logger) | ||||
|     logger.debug(f'Chosen keyring: {keyring.name}') | ||||
| 
 | ||||
|     if keyring == _LinuxKeyring.KWALLET: | ||||
|         return _get_kwallet_password(browser_keyring_name, logger) | ||||
|     elif keyring == _LinuxKeyring.GNOMEKEYRING: | ||||
|     if keyring in (_LinuxKeyring.KWALLET4, _LinuxKeyring.KWALLET5, _LinuxKeyring.KWALLET6): | ||||
|         return _get_kwallet_password(browser_keyring_name, keyring, logger) | ||||
|     elif keyring == _LinuxKeyring.GNOME_KEYRING: | ||||
|         return _get_gnome_keyring_password(browser_keyring_name, logger) | ||||
|     elif keyring == _LinuxKeyring.BASICTEXT: | ||||
|     elif keyring == _LinuxKeyring.BASIC_TEXT: | ||||
|         # when basic text is chosen, all cookies are stored as v10 (so no keyring password is required) | ||||
|         return None | ||||
|     assert False, f'Unknown keyring {keyring}' | ||||
| @@ -877,6 +958,10 @@ def _get_mac_keyring_password(browser_keyring_name, logger): | ||||
| 
 | ||||
| 
 | ||||
| def _get_windows_v10_key(browser_root, logger): | ||||
|     """ | ||||
|     References: | ||||
|         - [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc | ||||
|     """ | ||||
|     path = _find_most_recently_used_file(browser_root, 'Local State', logger) | ||||
|     if path is None: | ||||
|         logger.error('could not find local state file') | ||||
| @@ -885,11 +970,13 @@ def _get_windows_v10_key(browser_root, logger): | ||||
|     with open(path, encoding='utf8') as f: | ||||
|         data = json.load(f) | ||||
|     try: | ||||
|         # kOsCryptEncryptedKeyPrefName in [1] | ||||
|         base64_key = data['os_crypt']['encrypted_key'] | ||||
|     except KeyError: | ||||
|         logger.error('no encrypted key in Local State') | ||||
|         return None | ||||
|     encrypted_key = base64.b64decode(base64_key) | ||||
|     # kDPAPIKeyPrefix in [1] | ||||
|     prefix = b'DPAPI' | ||||
|     if not encrypted_key.startswith(prefix): | ||||
|         logger.error('invalid key') | ||||
| @@ -901,11 +988,13 @@ def pbkdf2_sha1(password, salt, iterations, key_length): | ||||
|     return pbkdf2_hmac('sha1', password, salt, iterations, key_length) | ||||
| 
 | ||||
| 
 | ||||
| def _decrypt_aes_cbc(ciphertext, key, logger, initialization_vector=b' ' * 16): | ||||
| def _decrypt_aes_cbc_multi(ciphertext, keys, logger, initialization_vector=b' ' * 16): | ||||
|     for key in keys: | ||||
|         plaintext = unpad_pkcs7(aes_cbc_decrypt_bytes(ciphertext, key, initialization_vector)) | ||||
|         try: | ||||
|             return plaintext.decode() | ||||
|         except UnicodeDecodeError: | ||||
|             pass | ||||
|     logger.warning('failed to decrypt cookie (AES-CBC) because UTF-8 decoding failed. Possibly the key is wrong?', only_once=True) | ||||
|     return None | ||||
| 
 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Matt Broadway
					Matt Broadway