Give a better error when a KDMFormatError is thrown (DoM #2446).
[libdcp.git] / tools / dcpdecryptmxf.cc
1 /*
2     Copyright (C) 2016-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 "atmos_asset.h"
36 #include "atmos_asset_reader.h"
37 #include "atmos_asset_writer.h"
38 #include "atmos_frame.h"
39 #include "crypto_context.h"
40 #include "decrypted_kdm.h"
41 #include "encrypted_kdm.h"
42 #include "exceptions.h"
43 #include "key.h"
44 #include "mono_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "util.h"
47 #include <asdcp/AS_DCP.h>
48 #include <getopt.h>
49 #include <iostream>
50 #include <string>
51
52
53 using std::cerr;
54 using std::cout;
55 using std::shared_ptr;
56 using std::string;
57 using boost::optional;
58
59
60 static void
61 help (string n)
62 {
63         cerr << "Re-write a MXF (decrypting it if required)\n"
64              << "Syntax: " << n << " [OPTION] <MXF>]\n"
65              << "  --version          show libdcp version\n"
66              << "  -v, --verbose      be verbose\n"
67              << "  -h, --help         show this help\n"
68              << "  -o, --output       output filename\n"
69              << "  -k, --kdm          KDM file\n"
70              << "  -p, --private-key  private key file\n"
71              << "  -t, --type         MXF type: picture or atmos\n"
72              << "  -i, --ignore-hmac  don't raise an error if HMACs don't agree\n";
73 }
74
75 template <class T, class U>
76 void copy (T const& in, shared_ptr<U> writer, bool ignore_hmac)
77 {
78         auto reader = in.start_read();
79         reader->set_check_hmac (!ignore_hmac);
80         for (int64_t i = 0; i < in.intrinsic_duration(); ++i) {
81                 auto frame = reader->get_frame (i);
82                 writer->write (frame->data(), frame->size());
83         }
84         writer->finalize();
85 };
86
87
88 int
89 main (int argc, char* argv[])
90 {
91         dcp::init ();
92
93         bool verbose = false;
94         optional<boost::filesystem::path> output_file;
95         optional<boost::filesystem::path> kdm_file;
96         optional<boost::filesystem::path> private_key_file;
97         bool ignore_hmac = false;
98
99         enum class Type {
100                 PICTURE,
101                 ATMOS,
102         };
103
104         optional<Type> type;
105
106         int option_index = 0;
107         while (true) {
108                 struct option long_options[] = {
109                         { "version", no_argument, 0, 'A' },
110                         { "verbose", no_argument, 0, 'v' },
111                         { "help", no_argument, 0, 'h' },
112                         { "output", required_argument, 0, 'o'},
113                         { "kdm", required_argument, 0, 'k'},
114                         { "private-key", required_argument, 0, 'p'},
115                         { "type", required_argument, 0, 't' },
116                         { "ignore-hmac", no_argument, 0, 'i' },
117                         { 0, 0, 0, 0 }
118                 };
119
120                 int c = getopt_long (argc, argv, "Avho:k:p:t:i", long_options, &option_index);
121
122                 if (c == -1) {
123                         break;
124                 }
125
126                 switch (c) {
127                 case 'A':
128                         cout << "libdcp version " << LIBDCP_VERSION << "\n";
129                         exit (EXIT_SUCCESS);
130                 case 'v':
131                         verbose = true;
132                         break;
133                 case 'h':
134                         help (argv[0]);
135                         exit (EXIT_SUCCESS);
136                 case 'o':
137                         output_file = optarg;
138                         break;
139                 case 'k':
140                         kdm_file = optarg;
141                         break;
142                 case 'p':
143                         private_key_file = optarg;
144                         break;
145                 case 't':
146                         if (strcmp(optarg, "picture") == 0) {
147                                 type = Type::PICTURE;
148                         } else if (strcmp(optarg, "atmos") == 0) {
149                                 type = Type::ATMOS;
150                         } else {
151                                 cerr << "Unknown MXF type " << optarg << "\n";
152                                 exit (EXIT_FAILURE);
153                         }
154                         break;
155                 case 'i':
156                         ignore_hmac = true;
157                         break;
158                 }
159         }
160
161         if (optind >= argc) {
162                 help (argv[0]);
163                 exit (EXIT_FAILURE);
164         }
165
166         boost::filesystem::path input_file = argv[optind];
167
168         if (!output_file) {
169                 cerr << "You must specify -o or --output\n";
170                 exit (EXIT_FAILURE);
171         }
172
173         if (!kdm_file) {
174                 cerr << "You must specify -k or --kdm\n";
175                 exit (EXIT_FAILURE);
176         }
177
178         if (!private_key_file) {
179                 cerr << "You must specify -p or --private-key\n";
180                 exit (EXIT_FAILURE);
181         }
182
183         if (!type) {
184                 cerr << "You must specify -t or --type\n";
185                 exit (EXIT_FAILURE);
186         }
187
188         dcp::EncryptedKDM encrypted_kdm (dcp::file_to_string (kdm_file.get ()));
189         dcp::DecryptedKDM decrypted_kdm (encrypted_kdm, dcp::file_to_string (private_key_file.get()));
190
191         auto add_key = [verbose](dcp::MXF& mxf, dcp::DecryptedKDM const& kdm) {
192                 auto key_id = mxf.key_id();
193                 if (key_id) {
194                         if (verbose) {
195                                 cout << "Asset is encrypted.\n";
196                         }
197                         auto keys = kdm.keys();
198                         auto key = std::find_if (keys.begin(), keys.end(), [key_id](dcp::DecryptedKDMKey const& k) { return k.id() == *key_id; });
199                         if (key == keys.end()) {
200                                 cout << "No key found in KDM.\n";
201                                 exit(EXIT_FAILURE);
202                         }
203                         if (verbose) {
204                                 cout << "Key found in KDM.\n";
205                         }
206                         mxf.set_key (key->key());
207                 }
208         };
209
210         try {
211                 switch (*type) {
212                 case Type::ATMOS:
213                 {
214                         dcp::AtmosAsset in (input_file);
215                         add_key (in, decrypted_kdm);
216                         dcp::AtmosAsset out (
217                                 in.edit_rate(),
218                                 in.first_frame(),
219                                 in.max_channel_count(),
220                                 in.max_object_count(),
221                                 in.atmos_version()
222                                 );
223                         auto writer = out.start_write (output_file.get());
224                         copy (in, writer, ignore_hmac);
225                         break;
226                 }
227                 case Type::PICTURE:
228                 {
229                         dcp::MonoPictureAsset in (input_file);
230                         add_key (in, decrypted_kdm);
231                         dcp::MonoPictureAsset out (in.edit_rate(), dcp::Standard::SMPTE);
232                         auto writer = out.start_write (output_file.get(), false);
233                         copy (in, writer, ignore_hmac);
234                         break;
235                 }
236                 }
237         } catch (dcp::ReadError& e) {
238                 cerr << "Read error: " << e.what() << "\n";
239                 return EXIT_FAILURE;
240         }
241
242         return 0;
243 }