2 Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "encrypted_kdm.h"
35 #include "decrypted_kdm.h"
36 #include "certificate_chain.h"
40 #include "mono_picture_asset.h"
41 #include "reel_mono_picture_asset.h"
45 #include "picture_asset_writer.h"
46 #include <libcxml/cxml.h>
47 #include <libxml++/libxml++.h>
48 #include <boost/test/unit_test.hpp>
53 using std::make_shared;
54 using std::shared_ptr;
55 using boost::optional;
57 /** Check reading and decryption of a KDM */
58 BOOST_AUTO_TEST_CASE (kdm_test)
60 dcp::DecryptedKDM kdm (
62 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
64 dcp::file_to_string ("test/data/private.key")
67 auto keys = kdm.keys ();
69 BOOST_CHECK_EQUAL (keys.size(), 2);
71 BOOST_CHECK_EQUAL (keys.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
72 BOOST_CHECK_EQUAL (keys.front().id(), "4ac4f922-8239-4831-b23b-31426d0542c4");
73 BOOST_CHECK_EQUAL (keys.front().key().hex(), "8a2729c3e5b65c45d78305462104c3fb");
75 BOOST_CHECK_EQUAL (keys.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
76 BOOST_CHECK_EQUAL (keys.back().id(), "73baf5de-e195-4542-ab28-8a465f7d4079");
77 BOOST_CHECK_EQUAL (keys.back().key().hex(), "5327fb7ec2e807bd57059615bf8a169d");
80 /** Check that we can read in a KDM and then write it back out again the same */
81 BOOST_AUTO_TEST_CASE (kdm_passthrough_test)
83 dcp::EncryptedKDM kdm (
84 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
87 auto parser = make_shared<xmlpp::DomParser>();
88 parser->parse_memory (kdm.as_xml ());
89 parser->get_document()->write_to_file_formatted ("build/kdm.xml", "UTF-8");
91 dcp::file_to_string("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
92 dcp::file_to_string("build/kdm.xml"),
98 /** Test some of the utility methods of DecryptedKDM */
99 BOOST_AUTO_TEST_CASE (decrypted_kdm_test)
101 auto data = new uint8_t[16];
103 dcp::DecryptedKDM::put_uuid (&p, "8971c838-d0c3-405d-bc57-43afa9d91242");
105 BOOST_CHECK_EQUAL (data[0], 0x89);
106 BOOST_CHECK_EQUAL (data[1], 0x71);
107 BOOST_CHECK_EQUAL (data[2], 0xc8);
108 BOOST_CHECK_EQUAL (data[3], 0x38);
109 BOOST_CHECK_EQUAL (data[4], 0xd0);
110 BOOST_CHECK_EQUAL (data[5], 0xc3);
111 BOOST_CHECK_EQUAL (data[6], 0x40);
112 BOOST_CHECK_EQUAL (data[7], 0x5d);
113 BOOST_CHECK_EQUAL (data[8], 0xbc);
114 BOOST_CHECK_EQUAL (data[9], 0x57);
115 BOOST_CHECK_EQUAL (data[10], 0x43);
116 BOOST_CHECK_EQUAL (data[11], 0xaf);
117 BOOST_CHECK_EQUAL (data[12], 0xa9);
118 BOOST_CHECK_EQUAL (data[13], 0xd9);
119 BOOST_CHECK_EQUAL (data[14], 0x12);
120 BOOST_CHECK_EQUAL (data[15], 0x42);
123 BOOST_CHECK_EQUAL (dcp::DecryptedKDM::get_uuid (&p), "8971c838-d0c3-405d-bc57-43afa9d91242");
128 /** Check that <KeyType> tags have the scope attribute.
129 * Wolfgang Woehl believes this is compulsory and I am more-or-less inclined to agree.
131 BOOST_AUTO_TEST_CASE (kdm_key_type_scope)
133 dcp::EncryptedKDM kdm (
134 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
138 doc.read_string (kdm.as_xml ());
140 auto typed_key_ids = doc.node_child("AuthenticatedPublic")->
141 node_child("RequiredExtensions")->
142 node_child("KDMRequiredExtensions")->
143 node_child("KeyIdList")->
144 node_children("TypedKeyId");
146 for (auto i: typed_key_ids) {
147 for (auto j: i->node_children("KeyType")) {
148 BOOST_CHECK (j->string_attribute("scope") == "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
153 static cxml::ConstNodePtr
154 kdm_forensic_test (cxml::Document& doc, bool picture, optional<int> audio)
156 dcp::DecryptedKDM decrypted (
158 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
160 dcp::file_to_string ("test/data/private.key")
163 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
164 signer->set_key(dcp::file_to_string("test/data/private.key"));
166 dcp::EncryptedKDM kdm = decrypted.encrypt (
167 signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, picture, audio
170 /* Check that we can pass this through correctly */
171 BOOST_CHECK_EQUAL (kdm.as_xml(), dcp::EncryptedKDM(kdm.as_xml()).as_xml());
173 doc.read_string (kdm.as_xml());
175 return doc.node_child("AuthenticatedPublic")->
176 node_child("RequiredExtensions")->
177 node_child("KDMRequiredExtensions")->
178 optional_node_child("ForensicMarkFlagList");
181 /** Check ForensicMarkFlagList handling: disable picture and all audio */
182 BOOST_AUTO_TEST_CASE (kdm_forensic_test1)
185 auto forensic = kdm_forensic_test(doc, true, 0);
186 BOOST_REQUIRE (forensic);
187 auto flags = forensic->node_children("ForensicMarkFlag");
188 BOOST_REQUIRE_EQUAL (flags.size(), 2);
189 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
190 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
193 /** Check ForensicMarkFlagList handling: disable picture but not audio */
194 BOOST_AUTO_TEST_CASE (kdm_forensic_test2)
197 auto forensic = kdm_forensic_test(doc, true, optional<int>());
198 BOOST_REQUIRE (forensic);
199 auto flags = forensic->node_children("ForensicMarkFlag");
200 BOOST_REQUIRE_EQUAL (flags.size(), 1);
201 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
204 /** Check ForensicMarkFlagList handling: disable audio but not picture */
205 BOOST_AUTO_TEST_CASE (kdm_forensic_test3)
208 auto forensic = kdm_forensic_test(doc, false, 0);
209 BOOST_REQUIRE (forensic);
210 auto flags = forensic->node_children("ForensicMarkFlag");
211 BOOST_REQUIRE_EQUAL (flags.size(), 1);
212 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
215 /** Check ForensicMarkFlagList handling: disable picture and audio above channel 3 */
216 BOOST_AUTO_TEST_CASE (kdm_forensic_test4)
219 auto forensic = kdm_forensic_test(doc, true, 3);
220 BOOST_REQUIRE (forensic);
221 auto flags = forensic->node_children("ForensicMarkFlag");
222 BOOST_REQUIRE_EQUAL (flags.size(), 2);
223 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
224 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable-above-channel-3");
227 /** Check ForensicMarkFlagList handling: disable neither */
228 BOOST_AUTO_TEST_CASE (kdm_forensic_test5)
231 auto forensic = kdm_forensic_test(doc, false, optional<int>());
232 BOOST_CHECK (!forensic);
235 /** Check that KDM validity periods are checked for being within the certificate validity */
236 BOOST_AUTO_TEST_CASE (validity_period_test1)
238 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
239 signer->set_key(dcp::file_to_string("test/data/private.key"));
241 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
242 asset->set_key (dcp::Key());
243 auto writer = asset->start_write ("build/test/validity_period_test1.mxf", false);
244 dcp::File frame ("test/data/flat_red.j2c");
245 writer->write (frame.data(), frame.size());
246 auto reel = make_shared<dcp::Reel>();
247 reel->add(make_shared<dcp::ReelMonoPictureAsset>(asset, 0));
248 auto cpl = make_shared<dcp::CPL>("test", dcp::FEATURE);
251 /* This certificate_chain is valid from 26/12/2012 to 24/12/2022 */
254 BOOST_CHECK_NO_THROW(
256 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("2015-01-01T00:00:00"), dcp::LocalTime("2017-07-31T00:00:00"), "", "", ""
257 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>())
260 /* Starts too early */
263 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("1981-01-01T00:00:00"), dcp::LocalTime("2017-07-31T00:00:00"), "", "", ""
264 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
268 /* Finishes too late */
271 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("2015-01-01T00:00:00"), dcp::LocalTime("2035-07-31T00:00:00"), "", "", ""
272 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
276 /* Starts too early and finishes too late */
279 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("1981-01-01T00:00:00"), dcp::LocalTime("2035-07-31T00:00:00"), "", "", ""
280 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),