115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
from hashlib import sha512
|
|
|
|
from vinetrimmer.utils.widevine.protos.widevine_pb2 import FileHashes
|
|
from vinetrimmer.utils.widevine.vmp import WidevineSignatureReader
|
|
|
|
"""
|
|
Script that generates a VMP blob for chromecdm
|
|
"""
|
|
|
|
WIN32_FILES = [
|
|
"chrome.exe",
|
|
"chrome.dll",
|
|
"chrome_child.dll",
|
|
"widevinecdmadapter.dll",
|
|
"widevinecdm.dll"
|
|
]
|
|
|
|
|
|
def sha512file(filename):
|
|
"""Compute SHA-512 digest of file."""
|
|
sha = sha512()
|
|
with open(filename, "rb") as fd:
|
|
for b in iter(lambda: fd.read(0x10000), b''):
|
|
sha.update(b)
|
|
return sha.digest()
|
|
|
|
|
|
def build_vmp_field(filenames):
|
|
"""
|
|
Create and fill out a FileHashes object.
|
|
|
|
`filenames` is an array of pairs of filenames like (file, file_signature)
|
|
such as ("module.dll", "module.dll.sig"). This does not validate the signature
|
|
against the codesign root CA, or even the sha512 hash against the current signature+signer
|
|
"""
|
|
file_hashes = FileHashes()
|
|
|
|
for basename, file, sig in filenames:
|
|
signature = WidevineSignatureReader.from_file(sig)
|
|
s = file_hashes.signatures.add()
|
|
s.filename = basename
|
|
s.test_signing = False # we can't check this without parsing signer
|
|
s.SHA512Hash = sha512file(file)
|
|
s.main_exe = signature.mainexe
|
|
s.signature = signature.signature
|
|
|
|
file_hashes.signer = signature.signer
|
|
return file_hashes.SerializeToString()
|
|
|
|
|
|
def get_files_with_signatures(path, required_files=None, random_order=False, sig_ext="sig"):
|
|
"""
|
|
use on chrome dir (a given version).
|
|
random_order would put any files it found in the dir with sigs,
|
|
it's not the right way to do it and the browser does not do this.
|
|
this function can still fail (generate wrong output) in subtle ways if
|
|
the Chrome dir has copies of the exe/sigs, especially if those copies are modified in some way
|
|
"""
|
|
if not required_files:
|
|
required_files = WIN32_FILES
|
|
|
|
all_files = []
|
|
sig_files = []
|
|
for dir_path, _, filenames in os.walk(path):
|
|
for filename in filenames:
|
|
full_path = os.path.join(dir_path, filename)
|
|
all_files.append(full_path)
|
|
if filename.endswith(sig_ext):
|
|
sig_files.append(full_path)
|
|
|
|
base_names = []
|
|
for path in sig_files:
|
|
orig_path = os.path.splitext(path)[0]
|
|
if orig_path not in all_files:
|
|
print("signature file {} lacks original file {}".format(path, orig_path))
|
|
base_names.append(path.name)
|
|
|
|
if not set(base_names).issuperset(set(required_files)):
|
|
# or should just make this warn as the next exception would be more specific
|
|
raise ValueError("Missing a binary/signature pair from {}".format(required_files))
|
|
|
|
files_to_hash = []
|
|
if random_order:
|
|
for path in sig_files:
|
|
orig_path = os.path.splitext(path)[0]
|
|
files_to_hash.append((os.path.basename(orig_path), orig_path, path))
|
|
else:
|
|
for basename in required_files:
|
|
found_file = False
|
|
for path in sig_files:
|
|
orig_path = os.path.splitext(path)[0]
|
|
if orig_path.endswith(basename):
|
|
files_to_hash.append((basename, orig_path, path))
|
|
found_file = True
|
|
break
|
|
if not found_file:
|
|
raise Exception("Failed to locate a file sig/pair for {}".format(basename))
|
|
|
|
return files_to_hash
|
|
|
|
|
|
def make_vmp_buff(browser_dir, file_msg_out):
|
|
with open(file_msg_out, "wb") as fd:
|
|
fd.write(build_vmp_field(get_files_with_signatures(browser_dir)))
|
|
|
|
|
|
if len(sys.argv) < 3:
|
|
print("Usage: {} BrowserPathWithVersion OutputPBMessage.bin".format(sys.argv[0]))
|
|
else:
|
|
make_vmp_buff(sys.argv[1], sys.argv[2])
|