Specify CPL standard on construction.
[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 #include "encrypted_kdm.h"
35 #include "decrypted_kdm.h"
36 #include "certificate_chain.h"
37 #include "util.h"
38 #include "test.h"
39 #include "cpl.h"
40 #include "mono_picture_asset.h"
41 #include "reel_mono_picture_asset.h"
42 #include "reel.h"
43 #include "types.h"
44 #include "picture_asset_writer.h"
45 #include <libcxml/cxml.h>
46 #include <libxml++/libxml++.h>
47 #include <boost/test/unit_test.hpp>
48
49 using std::list;
50 using std::string;
51 using std::vector;
52 using std::make_shared;
53 using std::shared_ptr;
54 using boost::optional;
55
56 /** Check reading and decryption of a KDM */
57 BOOST_AUTO_TEST_CASE (kdm_test)
58 {
59         dcp::DecryptedKDM kdm (
60                 dcp::EncryptedKDM (
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")
62                         ),
63                 dcp::file_to_string ("test/data/private.key")
64                 );
65
66         auto keys = kdm.keys ();
67
68         BOOST_CHECK_EQUAL (keys.size(), 2);
69
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");
73
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");
77 }
78
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)
81 {
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")
84                 );
85
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");
89         check_xml (
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"),
92                 {},
93                 true
94                 );
95 }
96
97 /** Test some of the utility methods of DecryptedKDM */
98 BOOST_AUTO_TEST_CASE (decrypted_kdm_test)
99 {
100         auto data = new uint8_t[16];
101         auto p = data;
102         dcp::DecryptedKDM::put_uuid (&p, "8971c838-d0c3-405d-bc57-43afa9d91242");
103
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);
120
121         p = data;
122         BOOST_CHECK_EQUAL (dcp::DecryptedKDM::get_uuid (&p), "8971c838-d0c3-405d-bc57-43afa9d91242");
123
124         delete[] data;
125 }
126
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.
129  */
130 BOOST_AUTO_TEST_CASE (kdm_key_type_scope)
131 {
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")
134                 );
135
136         cxml::Document doc;
137         doc.read_string (kdm.as_xml ());
138
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");
144
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");
148                 }
149         }
150 }
151
152 static cxml::ConstNodePtr
153 kdm_forensic_test (cxml::Document& doc, bool picture, optional<int> audio)
154 {
155         dcp::DecryptedKDM decrypted (
156                 dcp::EncryptedKDM (
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")
158                         ),
159                 dcp::file_to_string ("test/data/private.key")
160                 );
161
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"));
164
165         dcp::EncryptedKDM kdm = decrypted.encrypt (
166                 signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, picture, audio
167                 );
168
169         /* Check that we can pass this through correctly */
170         BOOST_CHECK_EQUAL (kdm.as_xml(), dcp::EncryptedKDM(kdm.as_xml()).as_xml());
171
172         doc.read_string (kdm.as_xml());
173
174         return doc.node_child("AuthenticatedPublic")->
175                 node_child("RequiredExtensions")->
176                 node_child("KDMRequiredExtensions")->
177                 optional_node_child("ForensicMarkFlagList");
178 }
179
180 /** Check ForensicMarkFlagList handling: disable picture and all audio */
181 BOOST_AUTO_TEST_CASE (kdm_forensic_test1)
182 {
183         cxml::Document doc;
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");
190 }
191
192 /** Check ForensicMarkFlagList handling: disable picture but not audio */
193 BOOST_AUTO_TEST_CASE (kdm_forensic_test2)
194 {
195         cxml::Document doc;
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");
201 }
202
203 /** Check ForensicMarkFlagList handling: disable audio but not picture */
204 BOOST_AUTO_TEST_CASE (kdm_forensic_test3)
205 {
206         cxml::Document doc;
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");
212 }
213
214 /** Check ForensicMarkFlagList handling: disable picture and audio above channel 3 */
215 BOOST_AUTO_TEST_CASE (kdm_forensic_test4)
216 {
217         cxml::Document doc;
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");
224 }
225
226 /** Check ForensicMarkFlagList handling: disable neither */
227 BOOST_AUTO_TEST_CASE (kdm_forensic_test5)
228 {
229         cxml::Document doc;
230         auto forensic = kdm_forensic_test(doc, false, optional<int>());
231         BOOST_CHECK (!forensic);
232 }
233
234 /** Check that KDM validity periods are checked for being within the certificate validity */
235 BOOST_AUTO_TEST_CASE (validity_period_test1)
236 {
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"));
239
240         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::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::ContentKind::FEATURE, dcp::Standard::SMPTE);
248         cpl->add(reel);
249
250         /* This certificate_chain is valid from 26/12/2012 to 24/12/2022 */
251
252         /* Inside */
253         BOOST_CHECK_NO_THROW(
254                 dcp::DecryptedKDM(
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::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>())
257                 );
258
259         /* Starts too early */
260         BOOST_CHECK_THROW(
261                 dcp::DecryptedKDM(
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::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
264                 dcp::BadKDMDateError
265                 );
266
267         /* Finishes too late */
268         BOOST_CHECK_THROW(
269                 dcp::DecryptedKDM(
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::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
272                 dcp::BadKDMDateError
273                 );
274
275         /* Starts too early and finishes too late */
276         BOOST_CHECK_THROW(
277                 dcp::DecryptedKDM(
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::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
280                 dcp::BadKDMDateError
281                 );
282 }