Cleanup: remove out-of-date comment.
[libdcp.git] / test / kdm_test.cc
1 /*
2     Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34
35 #include "certificate_chain.h"
36 #include "cpl.h"
37 #include "decrypted_kdm.h"
38 #include "encrypted_kdm.h"
39 #include "mono_picture_asset.h"
40 #include "picture_asset_writer.h"
41 #include "reel.h"
42 #include "reel_mono_picture_asset.h"
43 #include "reel_sound_asset.h"
44 #include "reel_smpte_subtitle_asset.h"
45 #include "smpte_subtitle_asset.h"
46 #include "test.h"
47 #include "types.h"
48 #include "util.h"
49 #include "warnings.h"
50 #include <libcxml/cxml.h>
51 LIBDCP_DISABLE_WARNINGS
52 #include <libxml++/libxml++.h>
53 LIBDCP_ENABLE_WARNINGS
54 #include <boost/test/unit_test.hpp>
55
56
57 using std::dynamic_pointer_cast;
58 using std::make_shared;
59 using std::shared_ptr;
60 using std::string;
61 using std::vector;
62 using boost::optional;
63
64
65 /** Check reading and decryption of a KDM */
66 BOOST_AUTO_TEST_CASE (kdm_test)
67 {
68         dcp::DecryptedKDM kdm (
69                 dcp::EncryptedKDM (
70                         dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
71                         ),
72                 dcp::file_to_string ("test/data/private.key")
73                 );
74
75         auto keys = kdm.keys ();
76
77         BOOST_CHECK_EQUAL (keys.size(), 2U);
78
79         BOOST_CHECK_EQUAL (keys.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
80         BOOST_CHECK_EQUAL (keys.front().id(), "4ac4f922-8239-4831-b23b-31426d0542c4");
81         BOOST_CHECK_EQUAL (keys.front().key().hex(), "8a2729c3e5b65c45d78305462104c3fb");
82
83         BOOST_CHECK_EQUAL (keys.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
84         BOOST_CHECK_EQUAL (keys.back().id(), "73baf5de-e195-4542-ab28-8a465f7d4079");
85         BOOST_CHECK_EQUAL (keys.back().key().hex(), "5327fb7ec2e807bd57059615bf8a169d");
86 }
87
88
89 /** Check that we can read in a KDM and then write it back out again the same */
90 BOOST_AUTO_TEST_CASE (kdm_passthrough_test)
91 {
92         dcp::EncryptedKDM kdm (
93                 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
94                 );
95
96         auto parser = make_shared<xmlpp::DomParser>();
97         parser->parse_memory (kdm.as_xml ());
98         parser->get_document()->write_to_file_formatted ("build/kdm.xml", "UTF-8");
99         check_xml (
100                 dcp::file_to_string("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
101                 dcp::file_to_string("build/kdm.xml"),
102                 {},
103                 true
104                 );
105 }
106
107
108 /** Test some of the utility methods of DecryptedKDM */
109 BOOST_AUTO_TEST_CASE (decrypted_kdm_test)
110 {
111         auto data = new uint8_t[16];
112         auto p = data;
113         dcp::DecryptedKDM::put_uuid (&p, "8971c838-d0c3-405d-bc57-43afa9d91242");
114
115         BOOST_CHECK_EQUAL (data[0], 0x89);
116         BOOST_CHECK_EQUAL (data[1], 0x71);
117         BOOST_CHECK_EQUAL (data[2], 0xc8);
118         BOOST_CHECK_EQUAL (data[3], 0x38);
119         BOOST_CHECK_EQUAL (data[4], 0xd0);
120         BOOST_CHECK_EQUAL (data[5], 0xc3);
121         BOOST_CHECK_EQUAL (data[6], 0x40);
122         BOOST_CHECK_EQUAL (data[7], 0x5d);
123         BOOST_CHECK_EQUAL (data[8], 0xbc);
124         BOOST_CHECK_EQUAL (data[9], 0x57);
125         BOOST_CHECK_EQUAL (data[10], 0x43);
126         BOOST_CHECK_EQUAL (data[11], 0xaf);
127         BOOST_CHECK_EQUAL (data[12], 0xa9);
128         BOOST_CHECK_EQUAL (data[13], 0xd9);
129         BOOST_CHECK_EQUAL (data[14], 0x12);
130         BOOST_CHECK_EQUAL (data[15], 0x42);
131
132         p = data;
133         BOOST_CHECK_EQUAL (dcp::DecryptedKDM::get_uuid (&p), "8971c838-d0c3-405d-bc57-43afa9d91242");
134
135         delete[] data;
136 }
137
138
139 /** Check that <KeyType> tags have the scope attribute.
140  *  Wolfgang Woehl believes this is compulsory and I am more-or-less inclined to agree.
141  */
142 BOOST_AUTO_TEST_CASE (kdm_key_type_scope)
143 {
144         dcp::EncryptedKDM kdm (
145                 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
146                 );
147
148         cxml::Document doc;
149         doc.read_string (kdm.as_xml ());
150
151         auto typed_key_ids = doc.node_child("AuthenticatedPublic")->
152                 node_child("RequiredExtensions")->
153                 node_child("KDMRequiredExtensions")->
154                 node_child("KeyIdList")->
155                 node_children("TypedKeyId");
156
157         for (auto i: typed_key_ids) {
158                 for (auto j: i->node_children("KeyType")) {
159                         BOOST_CHECK (j->string_attribute("scope") == "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
160                 }
161         }
162 }
163
164
165 static cxml::ConstNodePtr
166 kdm_forensic_test (cxml::Document& doc, bool picture, optional<int> audio)
167 {
168         dcp::DecryptedKDM decrypted (
169                 dcp::EncryptedKDM (
170                         dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
171                         ),
172                 dcp::file_to_string ("test/data/private.key")
173                 );
174
175         auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
176         signer->set_key(dcp::file_to_string("test/data/private.key"));
177
178         dcp::EncryptedKDM kdm = decrypted.encrypt (
179                 signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, picture, audio
180                 );
181
182         /* Check that we can pass this through correctly */
183         BOOST_CHECK_EQUAL (kdm.as_xml(), dcp::EncryptedKDM(kdm.as_xml()).as_xml());
184
185         doc.read_string (kdm.as_xml());
186
187         return doc.node_child("AuthenticatedPublic")->
188                 node_child("RequiredExtensions")->
189                 node_child("KDMRequiredExtensions")->
190                 optional_node_child("ForensicMarkFlagList");
191 }
192
193
194 /** Check ForensicMarkFlagList handling: disable picture and all audio */
195 BOOST_AUTO_TEST_CASE (kdm_forensic_test1)
196 {
197         cxml::Document doc;
198         auto forensic = kdm_forensic_test(doc, true, 0);
199         BOOST_REQUIRE (forensic);
200         auto flags = forensic->node_children("ForensicMarkFlag");
201         BOOST_REQUIRE_EQUAL (flags.size(), 2U);
202         BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
203         BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
204 }
205
206
207 /** Check ForensicMarkFlagList handling: disable picture but not audio */
208 BOOST_AUTO_TEST_CASE (kdm_forensic_test2)
209 {
210         cxml::Document doc;
211         auto forensic = kdm_forensic_test(doc, true, optional<int>());
212         BOOST_REQUIRE (forensic);
213         auto flags = forensic->node_children("ForensicMarkFlag");
214         BOOST_REQUIRE_EQUAL (flags.size(), 1U);
215         BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
216 }
217
218
219 /** Check ForensicMarkFlagList handling: disable audio but not picture */
220 BOOST_AUTO_TEST_CASE (kdm_forensic_test3)
221 {
222         cxml::Document doc;
223         auto forensic = kdm_forensic_test(doc, false, 0);
224         BOOST_REQUIRE (forensic);
225         auto flags = forensic->node_children("ForensicMarkFlag");
226         BOOST_REQUIRE_EQUAL (flags.size(), 1U);
227         BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
228 }
229
230
231 /** Check ForensicMarkFlagList handling: disable picture and audio above channel 3 */
232 BOOST_AUTO_TEST_CASE (kdm_forensic_test4)
233 {
234         cxml::Document doc;
235         auto forensic = kdm_forensic_test(doc, true, 3);
236         BOOST_REQUIRE (forensic);
237         auto flags = forensic->node_children("ForensicMarkFlag");
238         BOOST_REQUIRE_EQUAL (flags.size(), 2U);
239         BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
240         BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable-above-channel-3");
241 }
242
243
244 /** Check ForensicMarkFlagList handling: disable neither */
245 BOOST_AUTO_TEST_CASE (kdm_forensic_test5)
246 {
247         cxml::Document doc;
248         auto forensic = kdm_forensic_test(doc, false, optional<int>());
249         BOOST_CHECK (!forensic);
250 }
251
252
253 /** Check that KDM validity periods are checked for being within the certificate validity */
254 BOOST_AUTO_TEST_CASE (validity_period_test1)
255 {
256         auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
257         signer->set_key(dcp::file_to_string("test/data/private.key"));
258
259         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
260         asset->set_key (dcp::Key());
261         auto writer = asset->start_write("build/test/validity_period_test1.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
262         dcp::ArrayData frame ("test/data/flat_red.j2c");
263         writer->write (frame.data(), frame.size());
264         writer->finalize ();
265         auto reel = make_shared<dcp::Reel>();
266         reel->add(make_shared<dcp::ReelMonoPictureAsset>(asset, 0));
267         auto cpl = make_shared<dcp::CPL>("test", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
268         cpl->add(reel);
269
270         /* This certificate_chain is valid from 22/12/2022 to 19/12/2032 */
271
272         /* Inside */
273         BOOST_CHECK_NO_THROW(
274                 dcp::DecryptedKDM(
275                         cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("2025-01-01T00:00:00"), dcp::LocalTime("2027-07-31T00:00:00"), "", "", ""
276                         ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>())
277                 );
278
279         /* Starts too early */
280         BOOST_CHECK_THROW(
281                 dcp::DecryptedKDM(
282                         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"), "", "", ""
283                         ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
284                 dcp::BadKDMDateError
285                 );
286
287         /* Finishes too late */
288         BOOST_CHECK_THROW(
289                 dcp::DecryptedKDM(
290                         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"), "", "", ""
291                         ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
292                 dcp::BadKDMDateError
293                 );
294
295         /* Starts too early and finishes too late */
296         BOOST_CHECK_THROW(
297                 dcp::DecryptedKDM(
298                         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"), "", "", ""
299                         ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
300                 dcp::BadKDMDateError
301                 );
302 }
303
304
305 /** Test the case where we have:
306  *  - a OV + VF
307  *  - a single KDM which has keys for both OV and VF assets
308  *  and we load VF, then KDM, then the OV.  The OV's assets should have their
309  *  KDMs properly assigned.
310  */
311 BOOST_AUTO_TEST_CASE (vf_kdm_test)
312 {
313         /* Make OV */
314
315         boost::filesystem::path const ov_path = "build/test/vf_kdm_test_ov";
316         boost::filesystem::path const vf_path = "build/test/vf_kdm_test_vf";
317         dcp::Key key;
318
319         auto ov = make_simple(ov_path, 1, 48, dcp::Standard::SMPTE, key);
320         ov->write_xml ();
321
322         auto ov_reel = ov->cpls()[0]->reels()[0];
323         auto ov_reel_picture = make_shared<dcp::ReelMonoPictureAsset>(dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(ov_reel->main_picture())->mono_asset(), 0);
324         auto ov_reel_sound = make_shared<dcp::ReelSoundAsset>(ov_reel->main_sound()->asset(), 0);
325
326         /* Make VF */
327
328         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
329         subs->add(simple_subtitle());
330         subs->set_key(key);
331
332         boost::filesystem::remove_all (vf_path);
333         boost::filesystem::create_directory (vf_path);
334         dcp::ArrayData data(4096);
335         subs->add_font ("afont", data);
336         subs->write (vf_path / "subs.xml");
337
338         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
339
340         auto reel = make_shared<dcp::Reel>(ov_reel_picture, ov_reel_sound, reel_subs);
341
342         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
343         cpl->add (reel);
344
345         auto vf = make_shared<dcp::DCP>("build/test/vf_kdm_test_vf");
346         vf->add (cpl);
347         vf->write_xml ();
348
349         /* Make KDM for VF */
350
351         auto kdm = dcp::DecryptedKDM(
352                 cpl,
353                 key,
354                 dcp::LocalTime ("2016-01-01T00:00:00+00:00"),
355                 dcp::LocalTime ("2017-01-08T00:00:00+00:00"),
356                 "libdcp",
357                 "test",
358                 "2012-07-17T04:45:18+00:00"
359                 );
360
361         /* Decrypt VF with the KDM */
362
363         dcp::DCP reload_vf(vf_path);
364         reload_vf.read();
365         reload_vf.add (kdm);
366
367         /* Add the OV */
368
369         dcp::DCP reload_ov(ov_path);
370         reload_ov.read();
371         reload_vf.resolve_refs(reload_ov.assets());
372
373         /* Check that we can decrypt the VF */
374
375         BOOST_REQUIRE_EQUAL(reload_vf.cpls().size(), 1U);
376         BOOST_REQUIRE_EQUAL(reload_vf.cpls()[0]->reels().size(), 1U);
377         BOOST_REQUIRE(reload_vf.cpls()[0]->reels()[0]->main_picture());
378         BOOST_REQUIRE(reload_vf.cpls()[0]->reels()[0]->main_picture()->asset());
379         auto mono_asset = dynamic_pointer_cast<dcp::MonoPictureAsset>(reload_vf.cpls()[0]->reels()[0]->main_picture()->asset());
380         BOOST_REQUIRE(mono_asset);
381         auto reader = mono_asset->start_read();
382         reader->set_check_hmac(false);
383         reader->get_frame(0)->xyz_image();
384 }