fix(tracks): flag every encrypted sample entry type after decrypt

This commit is contained in:
imSp4rky
2026-10-23 17:14:12 -06:00
parent 9f054269db
commit d8663b0dad
3 changed files with 27 additions and 20 deletions
+9 -1
View File
@@ -1,5 +1,5 @@
"""Self-check for ``has_encrypted_sample_entry``: a finalized MP4 whose moov still carries
an encv/enca sample entry means the decrypt tool skipped/failed the track (frma was never
an encrypted (enc*/drm*) sample entry means the decrypt tool skipped/failed the track (frma was never
applied), so the detector must flag it before the file reaches the muxer. Detection is
scoped to moov, so stray 4CC byte runs in mdat must never trip (or mask) it.
@@ -11,6 +11,8 @@ from __future__ import annotations
import struct
from pathlib import Path
import pytest
from unshackle.core.tracks.track import has_encrypted_sample_entry
@@ -80,6 +82,12 @@ def test_text_containing_enca_in_moov_not_detected(tmp_path: Path) -> None:
assert has_encrypted_sample_entry(f) is False
@pytest.mark.parametrize("kind", [b"encs", b"enct", b"drmi"])
def test_other_encrypted_entry_kinds_detected(tmp_path: Path, kind: bytes) -> None:
f = write(tmp_path, "enc.mp4", FTYP, moov(stsd([sample_entry(kind, box(b"sinf", box(b"frma", b"stpp")))])))
assert has_encrypted_sample_entry(f) is True
def test_missing_or_bogus_file(tmp_path: Path) -> None:
assert has_encrypted_sample_entry(tmp_path / "nope.mp4") is False
bogus = write(tmp_path, "bogus.bin", b"\x00\x01\x02\x03not-an-mp4")
+10 -1
View File
@@ -11,6 +11,7 @@ PIFF_TENC = bytes.fromhex("8974dbce7be74c5184f97148f9882554")
PIFF_TFXD = bytes.fromhex("6d1d9b0542d544e680e2141daff757b2")
PIFF_SENC = bytes.fromhex("a2394f525a9b4f14a2446c427c648df4")
TENC = {b"tenc", b"uuid" + PIFF_TENC}
ENCRYPTED_ENTRY = (b"enc", b"drm")
SENC = {b"senc", b"uuid" + PIFF_SENC}
# the fixed sample entry fields before the child boxes: visual; sound v0 (and ISO v1), QuickTime sound v1 and v2
ENTRY_FIELDS = {b"encv": (78,), b"enca": (28, 44, 64)}
@@ -130,7 +131,15 @@ def sample_entries(data: bytes, body: int, end: int) -> tuple[list[Optional[UUID
whether any entry is encrypted."""
found = list(children(data, body + 8, end))
kids = [entry_kid(data, kind, entry, entry_end) for kind, entry, entry_end in found]
return kids, any(kind[:3] in (b"enc", b"drm") for kind, _, _ in found)
return kids, any(kind[:3] in ENCRYPTED_ENTRY for kind, _, _ in found)
def has_encrypted_entry(moov: bytes) -> bool:
"""True if a moov box holds a sample entry of an encrypted type (``enc*`` or ``drm*``)."""
return any(
kind == b"stsd" and any(entry[:3] in ENCRYPTED_ENTRY for entry, _, _ in children(moov, body + 8, end))
for kind, body, end in walk(moov)
)
def senc_protects_samples(buf: bytes, body: int, end: int) -> bool:
+8 -18
View File
@@ -30,7 +30,7 @@ from unshackle.core.config import config
from unshackle.core.constants import DOWNLOAD_ALL_DRM, DOWNLOAD_CANCELLED, DOWNLOAD_LICENCE_ONLY, DownloadCancelled
from unshackle.core.downloaders import requests
from unshackle.core.drm import DRM_T, ClearKeyCENC, PlayReady, Widevine
from unshackle.core.drm.verify import decrypt_track, senc_protects_samples
from unshackle.core.drm.verify import decrypt_track, has_encrypted_entry, senc_protects_samples
from unshackle.core.events import events
from unshackle.core.session import MAX_BACKOFF, RnetSession, utf8_text
from unshackle.core.temp import restore_segments, stash_segments
@@ -244,28 +244,18 @@ def has_dts_uhd_sample_entry(path: Path) -> bool:
def has_encrypted_sample_entry(path: Path) -> bool:
"""True if the MP4's moov still carries an encrypted sample entry (encv/enca).
"""True if the MP4's moov still carries an encrypted sample entry (``enc*`` or ``drm*``).
A faithful decrypt rewrites encv/enca back to the real codec 4CC through frma, so a
survivor means the decrypt tool skipped/failed the track. This is a 4CC scan
scoped to the moov box (where sample entries live), not a structural parse.
The scoping avoids chance byte collisions in mdat. Any read error -> False.
A faithful decrypt rewrites the entry back to the real codec 4CC through frma, so a
survivor means the decrypt tool skipped/failed the track. Only the moov is read, and its
stsd boxes are parsed, so byte runs in mdat or in metadata cannot match. Any read or
parse error -> False.
"""
try:
moov = read_top_level_box(path, b"moov")
except OSError:
return moov is not None and has_encrypted_entry(moov)
except (OSError, struct.error, RecursionError):
return False
if not moov:
return False
for tok in (b"encv", b"enca"):
i = moov.find(tok, 4)
while i != -1:
# require a plausible sample-entry box size right before the 4CC
size = int.from_bytes(moov[i - 4 : i], "big")
if 16 <= size <= len(moov) - (i - 4):
return True
i = moov.find(tok, i + 1)
return False
def moof_still_encrypted(moof: bytes) -> bool: