using youtube_dl implementation of AES-CBC

This commit is contained in:
Matthew Broadway 2021-06-03 22:33:25 +01:00
parent cec4807c3f
commit 2145835856
No known key found for this signature in database
GPG Key ID: DDC0B82B6896B381

View File

@ -8,8 +8,9 @@ import subprocess
import sys import sys
import warnings import warnings
from youtube_dl.aes import aes_cbc_decrypt
from youtube_dl.compat import compat_cookiejar_Cookie, compat_b64decode, Compat_TemporaryDirectory, compat_ord from youtube_dl.compat import compat_cookiejar_Cookie, compat_b64decode, Compat_TemporaryDirectory, compat_ord
from youtube_dl.utils import YoutubeDLCookieJar, expand_path from youtube_dl.utils import YoutubeDLCookieJar, expand_path, bytes_to_intlist, intlist_to_bytes
try: try:
from Crypto.Cipher import AES from Crypto.Cipher import AES
@ -254,12 +255,11 @@ def get_cookie_decryptor(browser_root, browser_keyring_name, logger):
class LinuxChromeCookieDecryptor(ChromeCookieDecryptor): class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
def __init__(self, browser_keyring_name): def __init__(self, browser_keyring_name):
self._v10_key = None self._v10_key = self.derive_key(b'peanuts')
self._v11_key = None if KEYRING_AVAILABLE:
if CRYPTO_AVAILABLE: self._v11_key = self.derive_key(_get_linux_keyring_password(browser_keyring_name))
self._v10_key = self.derive_key(b'peanuts') else:
if KEYRING_AVAILABLE: self._v11_key = None
self._v11_key = self.derive_key(_get_linux_keyring_password(browser_keyring_name))
@staticmethod @staticmethod
def derive_key(password): def derive_key(password):
@ -272,14 +272,11 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
ciphertext = encrypted_value[3:] ciphertext = encrypted_value[3:]
if version == b'v10': if version == b'v10':
if self._v10_key is None:
warnings.warn('cannot decrypt cookie as the module `pycryptodome` is not installed')
return None
return _decrypt_aes_cbc(ciphertext, self._v10_key) return _decrypt_aes_cbc(ciphertext, self._v10_key)
elif version == b'v11': elif version == b'v11':
if self._v11_key is None: if self._v11_key is None:
warnings.warn('cannot decrypt cookie as the `pycryptodome` or `keyring` modules are not installed') warnings.warn('cannot decrypt cookie as the `keyring` modules is not installed')
return None return None
return _decrypt_aes_cbc(ciphertext, self._v11_key) return _decrypt_aes_cbc(ciphertext, self._v11_key)
@ -289,9 +286,7 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
class MacChromeCookieDecryptor(ChromeCookieDecryptor): class MacChromeCookieDecryptor(ChromeCookieDecryptor):
def __init__(self, browser_keyring_name): def __init__(self, browser_keyring_name):
self._v10_key = None self._v10_key = self.derive_key(_get_mac_keyring_password(browser_keyring_name))
if CRYPTO_AVAILABLE:
self._v10_key = self.derive_key(_get_mac_keyring_password(browser_keyring_name))
@staticmethod @staticmethod
def derive_key(password): def derive_key(password):
@ -304,9 +299,6 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor):
ciphertext = encrypted_value[3:] ciphertext = encrypted_value[3:]
if version == b'v10': if version == b'v10':
if self._v10_key is None:
warnings.warn('cannot decrypt cookie as the `pycryptodome` module is not installed')
return None
return _decrypt_aes_cbc(ciphertext, self._v10_key) return _decrypt_aes_cbc(ciphertext, self._v10_key)
else: else:
@ -328,6 +320,9 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor):
if self._v10_key is None: if self._v10_key is None:
warnings.warn('cannot decrypt cookie') warnings.warn('cannot decrypt cookie')
return None return None
elif not CRYPTO_AVAILABLE:
warnings.warn('cannot decrypt cookie as the `pycryptodome` module is not installed')
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/os_crypt_win.cc
# kNonceLength # kNonceLength
@ -350,19 +345,18 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor):
def _get_linux_keyring_password(browser_keyring_name): def _get_linux_keyring_password(browser_keyring_name):
if KEYRING_AVAILABLE: password = keyring.get_password('{} Keys'.format(browser_keyring_name),
password = keyring.get_password('{} Keys'.format(browser_keyring_name), '{} Safe Storage'.format(browser_keyring_name))
'{} Safe Storage'.format(browser_keyring_name)) if password is None:
if password is None: # this sometimes occurs in KDE because chrome does not check hasEntry and instead
# this sometimes occurs in KDE because chrome does not check hasEntry and instead # just tries to read the value (which kwallet returns "") whereas keyring checks hasEntry
# just tries to read the value (which kwallet returns "") whereas keyring checks hasEntry # to verify this:
# to verify this: # dbus-monitor "interface='org.kde.KWallet'" "type=method_return"
# dbus-monitor "interface='org.kde.KWallet'" "type=method_return" # while starting chrome.
# while starting chrome. # this may be a bug as the intended behaviour is to generate a random password and store
# this may be a bug as the intended behaviour is to generate a random password and store # it, but that doesn't matter here.
# it, but that doesn't matter here. password = ''
password = '' return password.encode('utf-8')
return password.encode('utf-8')
def _get_mac_keyring_password(browser_keyring_name): def _get_mac_keyring_password(browser_keyring_name):
@ -404,11 +398,12 @@ def _get_windows_v10_password(browser_root, logger):
def _decrypt_aes_cbc(ciphertext, key, initialization_vector=b' ' * 16): def _decrypt_aes_cbc(ciphertext, key, initialization_vector=b' ' * 16):
cipher = AES.new(key, AES.MODE_CBC, iv=initialization_vector) plaintext = aes_cbc_decrypt(bytes_to_intlist(ciphertext),
plaintext = cipher.decrypt(ciphertext) bytes_to_intlist(key),
bytes_to_intlist(initialization_vector))
padding_length = compat_ord(plaintext[-1]) padding_length = compat_ord(plaintext[-1])
try: try:
return plaintext[:-padding_length].decode('utf-8') return intlist_to_bytes(plaintext[:-padding_length]).decode('utf-8')
except UnicodeDecodeError: except UnicodeDecodeError:
warnings.warn('failed to decrypt cookie because UTF-8 decoding failed. Possibly the key is wrong?') warnings.warn('failed to decrypt cookie because UTF-8 decoding failed. Possibly the key is wrong?')
return None return None