2 Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * @brief KDM and KDMKey classes.
26 #include "compose.hpp"
27 #include "exceptions.h"
31 #include "kdm_smpte_xml.h"
34 #include <libcxml/cxml.h>
35 #include <openssl/rsa.h>
36 #include <openssl/pem.h>
37 #include <openssl/err.h>
38 #include <boost/algorithm/string.hpp>
44 using std::stringstream;
49 using boost::shared_ptr;
52 KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key)
53 : _xml_kdm (new xml::DCinemaSecurityMessage (kdm))
55 /* Read the private key */
57 FILE* private_key_file = fopen_boost (private_key, "r");
58 if (!private_key_file) {
59 throw FileError ("could not find RSA private key file", private_key, errno);
62 RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0);
63 fclose (private_key_file);
65 throw FileError ("could not read RSA private key file", private_key, errno);
68 /* Use it to decrypt the keys */
70 list<string> encrypted_keys = _xml_kdm->authenticated_private.encrypted_keys;
72 for (list<string>::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
74 /* Decode the base-64-encoded cipher value from the KDM */
75 unsigned char cipher_value[256];
76 int const cipher_value_len = base64_decode (*i, cipher_value, sizeof (cipher_value));
79 unsigned char* decrypted = new unsigned char[RSA_size(rsa)];
80 int const decrypted_len = RSA_private_decrypt (cipher_value_len, cipher_value, decrypted, rsa, RSA_PKCS1_OAEP_PADDING);
81 if (decrypted_len == -1) {
83 throw MiscError (String::compose ("Could not decrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0)));
86 _keys.push_back (KDMKey (decrypted, decrypted_len));
94 boost::shared_ptr<const CPL> cpl,
95 boost::shared_ptr<const Signer> signer,
96 boost::shared_ptr<const Certificate> recipient_cert,
97 boost::posix_time::ptime not_valid_before, boost::posix_time::ptime not_valid_after,
98 string annotation_text, string issue_date
100 : _xml_kdm (new xml::DCinemaSecurityMessage)
102 xml::AuthenticatedPublic& apu = _xml_kdm->authenticated_public;
104 /* AuthenticatedPublic */
106 apu.message_id = "urn:uuid:" + make_uuid ();
107 apu.message_type = "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type";
108 apu.annotation_text = annotation_text;
109 apu.issue_date = issue_date;
110 apu.signer.x509_issuer_name = signer->certificates().leaf()->issuer ();
111 apu.signer.x509_serial_number = signer->certificates().leaf()->serial ();
112 apu.recipient.x509_issuer_serial.x509_issuer_name = recipient_cert->issuer ();
113 apu.recipient.x509_issuer_serial.x509_serial_number = recipient_cert->serial ();
114 apu.recipient.x509_subject_name = recipient_cert->subject ();
115 apu.composition_playlist_id = "urn:uuid:" + cpl->id ();
116 // apu.content_authenticator = signer->certificates().leaf()->thumbprint ();
117 apu.content_title_text = cpl->content_title_text ();
118 apu.content_keys_not_valid_before = ptime_to_string (not_valid_before);
119 apu.content_keys_not_valid_after = ptime_to_string (not_valid_after);
120 apu.authorized_device_info.device_list_identifier = "urn:uuid:" + make_uuid ();
121 string n = recipient_cert->common_name ();
122 if (n.find (".") != string::npos) {
123 n = n.substr (n.find (".") + 1);
125 apu.authorized_device_info.device_list_description = n;
126 // apu.authorized_device_info.device_list.push_back (recipient_cert->thumbprint ());
128 /* Sometimes digital_cinema_tools uses this magic thumbprint instead of that from an actual
129 recipient certificate. KDMs delivered to City Screen appear to use the same thing.
131 apu.authorized_device_info.device_list.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
133 list<shared_ptr<const Content> > assets = cpl->assets ();
134 for (list<shared_ptr<const Content> >::iterator i = assets.begin(); i != assets.end(); ++i) {
135 /* XXX: non-MXF assets? */
136 shared_ptr<const MXF> mxf = boost::dynamic_pointer_cast<const MXF> (*i);
138 apu.key_id_list.push_back (xml::TypedKeyId (mxf->key_type(), "urn:uuid:" + mxf->key_id()));
142 apu.forensic_mark_flag_list.push_back ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
143 apu.forensic_mark_flag_list.push_back ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
145 /* AuthenticatedPrivate */
147 for (list<shared_ptr<const Content> >::iterator i = assets.begin(); i != assets.end(); ++i) {
148 /* XXX: non-MXF assets? */
149 shared_ptr<const MXF> mxf = boost::dynamic_pointer_cast<const MXF> (*i);
152 signer, cpl->id (), mxf->key_type (), mxf->key_id (),
153 not_valid_before, not_valid_after, mxf->key().get()
156 _keys.push_back (kkey);
157 _xml_kdm->authenticated_private.encrypted_keys.push_back (kkey.encrypted_base64 (recipient_cert));
163 shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
164 shared_ptr<cxml::Node> root (new cxml::Node (doc->get_root_node ()));
165 xmlpp::Node* signature = root->node_child("Signature")->node();
166 signer->add_signature_value (signature, "ds");
167 _xml_kdm->signature = xml::Signature (shared_ptr<cxml::Node> (new cxml::Node (signature)));
170 KDM::KDM (KDM const & other)
171 : _keys (other._keys)
172 , _xml_kdm (new xml::DCinemaSecurityMessage (*other._xml_kdm.get()))
178 KDM::operator= (KDM const & other)
180 if (this == &other) {
185 _xml_kdm.reset (new xml::DCinemaSecurityMessage (*other._xml_kdm.get ()));
191 KDM::as_xml (boost::filesystem::path path) const
193 shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
194 /* This must *not* be the _formatted version, otherwise the signature
197 doc->write_to_file (path.string(), "UTF-8");
203 shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
204 /* This must *not* be the _formatted version, otherwise the signature
207 return doc->write_to_string ("UTF-8");
211 boost::shared_ptr<const Signer> signer,
215 boost::posix_time::ptime from,
216 boost::posix_time::ptime until,
220 , _key_type (key_type)
222 , _not_valid_before (ptime_to_string (from))
223 , _not_valid_after (ptime_to_string (until))
226 base64_decode (signer->certificates().leaf()->thumbprint (), _signer_thumbprint, 20);
229 KDMKey::KDMKey (uint8_t const * raw, int len)
234 /* [0-15] is structure id (fixed sequence specified by standard) */
236 get (_signer_thumbprint, &raw, 20);
237 _cpl_id = get_uuid (&raw);
238 _key_id = get_uuid (&raw);
239 _not_valid_before = get (&raw, 25);
240 _not_valid_after = get (&raw, 25);
245 /* [0-15] is structure id (fixed sequence specified by standard) */
247 get (_signer_thumbprint, &raw, 20);
248 _cpl_id = get_uuid (&raw);
249 _key_type = get (&raw, 4);
250 _key_id = get_uuid (&raw);
251 _not_valid_before = get (&raw, 25);
252 _not_valid_after = get (&raw, 25);
260 KDMKey::KDMKey (KDMKey const & other)
261 : _cpl_id (other._cpl_id)
262 , _key_type (other._key_type)
263 , _key_id (other._key_id)
264 , _not_valid_before (other._not_valid_before)
265 , _not_valid_after (other._not_valid_after)
268 memcpy (_signer_thumbprint, other._signer_thumbprint, 20);
272 KDMKey::operator= (KDMKey const & other)
274 if (&other == this) {
278 _cpl_id = other._cpl_id;
279 _key_type = other._key_type;
280 _key_id = other._key_id;
281 _not_valid_before = other._not_valid_before;
282 _not_valid_after = other._not_valid_after;
284 memcpy (_signer_thumbprint, other._signer_thumbprint, 20);
290 KDMKey::encrypted_base64 (boost::shared_ptr<const Certificate> recipient_cert) const
292 assert (_key_type.length() == 4);
293 assert (_not_valid_before.length() == 25);
294 assert (_not_valid_after.length() == 25);
296 /* XXX: SMPTE only */
300 /* Magic value specified by SMPTE S430-1-2006 */
301 uint8_t structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab };
302 put (&p, structure_id, 16);
303 put (&p, _signer_thumbprint, 20);
304 put_uuid (&p, _cpl_id);
306 put_uuid (&p, _key_id);
307 put (&p, _not_valid_before);
308 put (&p, _not_valid_after);
309 put (&p, _key.value(), ASDCP::KeyLen);
311 /* Encrypt using the projector's public key */
312 RSA* rsa = recipient_cert->public_key ();
313 unsigned char encrypted[RSA_size(rsa)];
314 int const encrypted_len = RSA_public_encrypt (p - block, block, encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
315 if (encrypted_len == -1) {
316 throw MiscError (String::compose ("Could not encrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0)));
319 /* Lazy overallocation */
320 char out[encrypted_len * 2];
321 Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2);
322 int const N = strlen (out);
324 for (int i = 0; i < N; ++i) {
325 if (i > 0 && (i % 64) == 0) {
335 KDMKey::get (uint8_t const ** p, int N) const
338 for (int i = 0; i < N; ++i) {
347 KDMKey::get (uint8_t* o, uint8_t const ** p, int N) const
354 KDMKey::get_uuid (unsigned char const ** p) const
358 for (int i = 0; i < 16; ++i) {
359 g << setw(2) << setfill('0') << hex << static_cast<int> (**p);
361 if (i == 3 || i == 5 || i == 7 || i == 9) {
370 KDMKey::put (uint8_t ** d, uint8_t const * s, int N) const
377 KDMKey::put (uint8_t ** d, string s) const
379 memcpy (*d, s.c_str(), s.length());
384 KDMKey::put_uuid (uint8_t ** d, string id) const
386 id.erase (std::remove (id.begin(), id.end(), '-'));
387 for (int i = 0; i < 32; i += 2) {
389 s << id[i] << id[i + 1];
398 dcp::operator== (dcp::KDMKey const & a, dcp::KDMKey const & b)
400 if (memcmp (a._signer_thumbprint, b._signer_thumbprint, 20) != 0) {
405 a._cpl_id == b._cpl_id &&
406 a._key_type == b._key_type &&
407 a._key_id == b._key_id &&
408 a._not_valid_before == b._not_valid_before &&
409 a._not_valid_after == b._not_valid_after &&