Bv2.1 9.2: PKL must be signed if it contains encrypted assets.
authorCarl Hetherington <cth@carlh.net>
Mon, 18 Jan 2021 23:05:44 +0000 (00:05 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 18 Jan 2021 23:10:24 +0000 (00:10 +0100)
src/verify.cc
src/verify.h
test/verify_test.cc

index 901f44bc400fb0e44b1e0bbe704ee98e2f05d6fc..26173a5b46b547574396a7c2f830e439bb6b4bad 100644 (file)
@@ -1061,6 +1061,36 @@ check_extension_metadata (shared_ptr<dcp::CPL> cpl, vector<VerificationNote>& no
 }
 
 
+bool
+pkl_has_encrypted_assets (shared_ptr<DCP> dcp, shared_ptr<PKL> pkl)
+{
+       vector<string> encrypted;
+       for (auto i: dcp->cpls()) {
+               for (auto j: i->reel_mxfs()) {
+                       if (j->asset_ref().resolved()) {
+                               /* It's a bit surprising / broken but Interop subtitle assets are represented
+                                * in reels by ReelSubtitleAsset which inherits ReelMXF, so it's possible for
+                                * ReelMXFs to have assets which are not MXFs.
+                                */
+                               if (auto asset = dynamic_pointer_cast<MXF>(j->asset_ref().asset())) {
+                                       if (asset->encrypted()) {
+                                               encrypted.push_back(j->asset_ref().id());
+                                       }
+                               }
+                       }
+               }
+       }
+
+       for (auto i: pkl->asset_list()) {
+               if (find(encrypted.begin(), encrypted.end(), i->id()) != encrypted.end()) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
 vector<VerificationNote>
 dcp::verify (
        vector<boost::filesystem::path> directories,
@@ -1336,6 +1366,13 @@ dcp::verify (
                for (auto pkl: dcp->pkls()) {
                        stage ("Checking PKL", pkl->file());
                        validate_xml (pkl->file().get(), xsd_dtd_directory, notes);
+                       if (pkl_has_encrypted_assets(dcp, pkl)) {
+                               cxml::Document doc ("PackingList");
+                               doc.read_file (pkl->file().get());
+                               if (!doc.optional_node_child("Signature")) {
+                                       notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::PKL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED, pkl->file().get()});
+                               }
+                       }
                }
 
                if (dcp->asset_map_path()) {
@@ -1473,6 +1510,8 @@ dcp::note_to_string (dcp::VerificationNote note)
                return String::compose("The <ExtensionMetadata> is malformed in some way: %1", note.note().get());
        case dcp::VerificationNote::CPL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED:
                return String::compose("The CPL %1, which has encrypted content, is not signed", note.file()->filename());
+       case dcp::VerificationNote::PKL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED:
+               return String::compose("The PKL %1, which has encrypted content, is not signed", note.file()->filename());
        case dcp::VerificationNote::PKL_ANNOTATION_TEXT_DOES_NOT_MATCH_CPL_CONTENT_TITLE_TEXT:
                return String::compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>", note.file()->filename());
        }
index 46c7b2e3d92f416a0273841b5e27e00ddfbe8885..a48b65f3d11bde048055fcd9c7d1c8b975506d82 100644 (file)
@@ -175,6 +175,8 @@ public:
                INVALID_EXTENSION_METADATA,
                /** CPLs containing encrypted content must be signed Bv2.1_8.7 */
                CPL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED,
+               /** PKLs containing encrypted content must be signed Bv2.1_8.7 */
+               PKL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED,
                /** If a PKL has one CPL its <ContentTitleText> must be the same as the PKL's <AnnotationText> */
                PKL_ANNOTATION_TEXT_DOES_NOT_MATCH_CPL_CONTENT_TITLE_TEXT
        };
index 265529bb059c5ddb26e835cbe8cf1879146512f0..f0c6b644e6bf3285e6f84060ad8c943c650cbe51 100644 (file)
@@ -196,6 +196,7 @@ void
 check_verify_result (vector<boost::filesystem::path> dir, vector<dcp::VerificationNote> test_notes)
 {
        auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+       dump_notes (notes);
        BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
 }
 
@@ -2577,3 +2578,47 @@ BOOST_AUTO_TEST_CASE (verify_encrypted_cpl_is_signed)
                });
 }
 
+
+BOOST_AUTO_TEST_CASE (verify_encrypted_pkl_is_signed)
+{
+       boost::filesystem::path dir = "build/test/verify_encrypted_pkl_is_signed";
+       prepare_directory (dir);
+       for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/encryption_test")) {
+               boost::filesystem::copy_file (i.path(), dir / i.path().filename());
+       }
+
+       {
+               Editor e (dir / "pkl_93182bd2-b1e8-41a3-b5c8-6e6564273bff.xml");
+               e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
+       }
+
+       check_verify_result (
+               {dir},
+               {
+                       { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PKL_ANNOTATION_TEXT_DOES_NOT_MATCH_CPL_CONTENT_TITLE_TEXT },
+                       { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
+                       { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
+                       { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
+                       { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
+                       { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_CPL_METADATA },
+                       { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PKL_WITH_ENCRYPTED_CONTENT_NOT_SIGNED }
+               });
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_unencrypted_pkl_can_be_unsigned)
+{
+       boost::filesystem::path dir = "build/test/verify_unencrypted_pkl_can_be_unsigned";
+       prepare_directory (dir);
+       for (auto i: boost::filesystem::directory_iterator("test/ref/DCP/dcp_test1")) {
+               boost::filesystem::copy_file (i.path(), dir / i.path().filename());
+       }
+
+       {
+               Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
+               e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
+       }
+
+       check_verify_result ({dir}, {});
+}
+