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"
44 #include "picture_asset_writer.h"
45 #include <libcxml/cxml.h>
46 #include <libxml++/libxml++.h>
47 #include <boost/test/unit_test.hpp>
52 using std::make_shared;
53 using std::shared_ptr;
54 using boost::optional;
56 /** Check reading and decryption of a KDM */
57 BOOST_AUTO_TEST_CASE (kdm_test)
59 dcp::DecryptedKDM kdm (
61 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
63 dcp::file_to_string ("test/data/private.key")
66 auto keys = kdm.keys ();
68 BOOST_CHECK_EQUAL (keys.size(), 2);
70 BOOST_CHECK_EQUAL (keys.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
71 BOOST_CHECK_EQUAL (keys.front().id(), "4ac4f922-8239-4831-b23b-31426d0542c4");
72 BOOST_CHECK_EQUAL (keys.front().key().hex(), "8a2729c3e5b65c45d78305462104c3fb");
74 BOOST_CHECK_EQUAL (keys.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
75 BOOST_CHECK_EQUAL (keys.back().id(), "73baf5de-e195-4542-ab28-8a465f7d4079");
76 BOOST_CHECK_EQUAL (keys.back().key().hex(), "5327fb7ec2e807bd57059615bf8a169d");
79 /** Check that we can read in a KDM and then write it back out again the same */
80 BOOST_AUTO_TEST_CASE (kdm_passthrough_test)
82 dcp::EncryptedKDM kdm (
83 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
86 auto parser = make_shared<xmlpp::DomParser>();
87 parser->parse_memory (kdm.as_xml ());
88 parser->get_document()->write_to_file_formatted ("build/kdm.xml", "UTF-8");
90 dcp::file_to_string("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
91 dcp::file_to_string("build/kdm.xml"),
97 /** Test some of the utility methods of DecryptedKDM */
98 BOOST_AUTO_TEST_CASE (decrypted_kdm_test)
100 auto data = new uint8_t[16];
102 dcp::DecryptedKDM::put_uuid (&p, "8971c838-d0c3-405d-bc57-43afa9d91242");
104 BOOST_CHECK_EQUAL (data[0], 0x89);
105 BOOST_CHECK_EQUAL (data[1], 0x71);
106 BOOST_CHECK_EQUAL (data[2], 0xc8);
107 BOOST_CHECK_EQUAL (data[3], 0x38);
108 BOOST_CHECK_EQUAL (data[4], 0xd0);
109 BOOST_CHECK_EQUAL (data[5], 0xc3);
110 BOOST_CHECK_EQUAL (data[6], 0x40);
111 BOOST_CHECK_EQUAL (data[7], 0x5d);
112 BOOST_CHECK_EQUAL (data[8], 0xbc);
113 BOOST_CHECK_EQUAL (data[9], 0x57);
114 BOOST_CHECK_EQUAL (data[10], 0x43);
115 BOOST_CHECK_EQUAL (data[11], 0xaf);
116 BOOST_CHECK_EQUAL (data[12], 0xa9);
117 BOOST_CHECK_EQUAL (data[13], 0xd9);
118 BOOST_CHECK_EQUAL (data[14], 0x12);
119 BOOST_CHECK_EQUAL (data[15], 0x42);
122 BOOST_CHECK_EQUAL (dcp::DecryptedKDM::get_uuid (&p), "8971c838-d0c3-405d-bc57-43afa9d91242");
127 /** Check that <KeyType> tags have the scope attribute.
128 * Wolfgang Woehl believes this is compulsory and I am more-or-less inclined to agree.
130 BOOST_AUTO_TEST_CASE (kdm_key_type_scope)
132 dcp::EncryptedKDM kdm (
133 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
137 doc.read_string (kdm.as_xml ());
139 auto typed_key_ids = doc.node_child("AuthenticatedPublic")->
140 node_child("RequiredExtensions")->
141 node_child("KDMRequiredExtensions")->
142 node_child("KeyIdList")->
143 node_children("TypedKeyId");
145 for (auto i: typed_key_ids) {
146 for (auto j: i->node_children("KeyType")) {
147 BOOST_CHECK (j->string_attribute("scope") == "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
152 static cxml::ConstNodePtr
153 kdm_forensic_test (cxml::Document& doc, bool picture, optional<int> audio)
155 dcp::DecryptedKDM decrypted (
157 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
159 dcp::file_to_string ("test/data/private.key")
162 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
163 signer->set_key(dcp::file_to_string("test/data/private.key"));
165 dcp::EncryptedKDM kdm = decrypted.encrypt (
166 signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, picture, audio
169 /* Check that we can pass this through correctly */
170 BOOST_CHECK_EQUAL (kdm.as_xml(), dcp::EncryptedKDM(kdm.as_xml()).as_xml());
172 doc.read_string (kdm.as_xml());
174 return doc.node_child("AuthenticatedPublic")->
175 node_child("RequiredExtensions")->
176 node_child("KDMRequiredExtensions")->
177 optional_node_child("ForensicMarkFlagList");
180 /** Check ForensicMarkFlagList handling: disable picture and all audio */
181 BOOST_AUTO_TEST_CASE (kdm_forensic_test1)
184 auto forensic = kdm_forensic_test(doc, true, 0);
185 BOOST_REQUIRE (forensic);
186 auto flags = forensic->node_children("ForensicMarkFlag");
187 BOOST_REQUIRE_EQUAL (flags.size(), 2);
188 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
189 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
192 /** Check ForensicMarkFlagList handling: disable picture but not audio */
193 BOOST_AUTO_TEST_CASE (kdm_forensic_test2)
196 auto forensic = kdm_forensic_test(doc, true, optional<int>());
197 BOOST_REQUIRE (forensic);
198 auto flags = forensic->node_children("ForensicMarkFlag");
199 BOOST_REQUIRE_EQUAL (flags.size(), 1);
200 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
203 /** Check ForensicMarkFlagList handling: disable audio but not picture */
204 BOOST_AUTO_TEST_CASE (kdm_forensic_test3)
207 auto forensic = kdm_forensic_test(doc, false, 0);
208 BOOST_REQUIRE (forensic);
209 auto flags = forensic->node_children("ForensicMarkFlag");
210 BOOST_REQUIRE_EQUAL (flags.size(), 1);
211 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
214 /** Check ForensicMarkFlagList handling: disable picture and audio above channel 3 */
215 BOOST_AUTO_TEST_CASE (kdm_forensic_test4)
218 auto forensic = kdm_forensic_test(doc, true, 3);
219 BOOST_REQUIRE (forensic);
220 auto flags = forensic->node_children("ForensicMarkFlag");
221 BOOST_REQUIRE_EQUAL (flags.size(), 2);
222 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
223 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable-above-channel-3");
226 /** Check ForensicMarkFlagList handling: disable neither */
227 BOOST_AUTO_TEST_CASE (kdm_forensic_test5)
230 auto forensic = kdm_forensic_test(doc, false, optional<int>());
231 BOOST_CHECK (!forensic);
234 /** Check that KDM validity periods are checked for being within the certificate validity */
235 BOOST_AUTO_TEST_CASE (validity_period_test1)
237 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
238 signer->set_key(dcp::file_to_string("test/data/private.key"));
240 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
241 asset->set_key (dcp::Key());
242 auto writer = asset->start_write ("build/test/validity_period_test1.mxf", false);
243 dcp::ArrayData frame ("test/data/flat_red.j2c");
244 writer->write (frame.data(), frame.size());
245 auto reel = make_shared<dcp::Reel>();
246 reel->add(make_shared<dcp::ReelMonoPictureAsset>(asset, 0));
247 auto cpl = make_shared<dcp::CPL>("test", dcp::FEATURE);
250 /* This certificate_chain is valid from 26/12/2012 to 24/12/2022 */
253 BOOST_CHECK_NO_THROW(
255 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"), "", "", ""
256 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>())
259 /* Starts too early */
262 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"), "", "", ""
263 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
267 /* Finishes too late */
270 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"), "", "", ""
271 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
275 /* Starts too early and finishes too late */
278 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"), "", "", ""
279 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::MODIFIED_TRANSITIONAL_1, true, optional<int>()),