From 7702e5d643440e75369078863b34f8a574ee4143 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 19 Mar 2014 21:46:01 +0000 Subject: [PATCH] Considerable re-work of KDM class to express the difference between encrypted and unencrypted KDMs. --- examples/make_dcp.cc | 1 + src/cpl.cc | 7 +- src/cpl.h | 5 +- src/dcp.cc | 9 +- src/dcp.h | 4 +- src/decrypted_kdm.cc | 276 ++++++++ src/decrypted_kdm.h | 66 ++ .../decrypted_kdm_key.cc | 18 +- src/decrypted_kdm_key.h | 64 ++ src/encrypted_kdm.cc | 591 ++++++++++++++++++ src/encrypted_kdm.h | 69 ++ src/exceptions.cc | 7 + src/exceptions.h | 20 +- src/file.cc | 1 + src/kdm.cc | 477 -------------- src/kdm.h | 226 ------- src/local_time.cc | 102 +++ src/local_time.h | 53 ++ src/metadata.cc | 8 +- src/mxf.cc | 2 - src/reel.cc | 13 +- src/reel.h | 4 +- src/reel_picture_asset.cc | 1 + src/rgb_xyz.cc | 1 + src/util.cc | 60 -- src/util.h | 4 - src/wscript | 8 +- test/decryption_test.cc | 13 +- test/encryption_test.cc | 14 +- test/kdm_key_test.cc | 49 -- test/kdm_test.cc | 30 +- test/local_time_test.cc | 63 ++ test/round_trip_test.cc | 24 +- test/wscript | 3 +- 34 files changed, 1393 insertions(+), 900 deletions(-) create mode 100644 src/decrypted_kdm.cc create mode 100644 src/decrypted_kdm.h rename test/utc_offset_to_string_test.cc => src/decrypted_kdm_key.cc (56%) create mode 100644 src/decrypted_kdm_key.h create mode 100644 src/encrypted_kdm.cc create mode 100644 src/encrypted_kdm.h delete mode 100644 src/kdm.cc delete mode 100644 src/kdm.h create mode 100644 src/local_time.cc create mode 100644 src/local_time.h delete mode 100644 test/kdm_key_test.cc create mode 100644 test/local_time_test.cc diff --git a/examples/make_dcp.cc b/examples/make_dcp.cc index d86cd30a..910fb8cb 100644 --- a/examples/make_dcp.cc +++ b/examples/make_dcp.cc @@ -41,6 +41,7 @@ #include "file.h" #include "reel_mono_picture_asset.h" #include "reel_sound_asset.h" +#include int main () diff --git a/src/cpl.cc b/src/cpl.cc index 9eae09ad..612ff479 100644 --- a/src/cpl.cc +++ b/src/cpl.cc @@ -32,6 +32,7 @@ #include "reel_picture_asset.h" #include "reel_sound_asset.h" #include "reel_subtitle_asset.h" +#include "local_time.h" #include using std::string; @@ -55,9 +56,7 @@ CPL::CPL (string annotation_text, ContentKind content_kind) /* default _content_version_id to and _content_version_label to a random ID and the current time. */ - time_t now = time (0); - struct tm* tm = localtime (&now); - _content_version_id = "urn:uuid:" + make_uuid() + tm_to_string (tm); + _content_version_id = "urn:uuid:" + make_uuid() + LocalTime().as_string (); _content_version_label_text = _content_version_id; } @@ -226,7 +225,7 @@ CPL::encrypted () const * @param kdm KDM. */ void -CPL::add (KDM const & kdm) +CPL::add (DecryptedKDM const & kdm) { for (list >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { (*i)->add (kdm); diff --git a/src/cpl.h b/src/cpl.h index 8458a028..3bff4c4e 100644 --- a/src/cpl.h +++ b/src/cpl.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -40,7 +39,7 @@ class Reel; class XMLMetadata; class MXFMetadata; class Signer; -class KDM; +class DecryptedKDM; /** @class CPL * @brief A Composition Playlist. @@ -58,7 +57,7 @@ public: ) const; void add (boost::shared_ptr reel); - void add (KDM const &); + void add (DecryptedKDM const &); /** @return contents of the <AnnotationText> node */ std::string annotation_text () const { diff --git a/src/dcp.cc b/src/dcp.cc index 88e36506..817cf36e 100644 --- a/src/dcp.cc +++ b/src/dcp.cc @@ -33,9 +33,10 @@ #include "reel.h" #include "cpl.h" #include "signer.h" -#include "kdm.h" #include "compose.hpp" #include "AS_DCP.h" +#include "decrypted_kdm.h" +#include "decrypted_kdm_key.h" #include #include #include @@ -194,13 +195,13 @@ DCP::encrypted () const } void -DCP::add (KDM const & kdm) +DCP::add (DecryptedKDM const & kdm) { - list keys = kdm.keys (); + list keys = kdm.keys (); list > cpl = cpls (); for (list >::iterator i = cpl.begin(); i != cpl.end(); ++i) { - for (list::iterator j = keys.begin(); j != keys.end(); ++j) { + for (list::iterator j = keys.begin(); j != keys.end(); ++j) { if (j->cpl_id() == (*i)->id()) { (*i)->add (kdm); } diff --git a/src/dcp.h b/src/dcp.h index d6275037..450a3c00 100644 --- a/src/dcp.h +++ b/src/dcp.h @@ -46,7 +46,7 @@ class Reel; class CPL; class XMLMetadata; class Signer; -class KDM; +class DecryptedKDM; class Asset; namespace parse { @@ -89,7 +89,7 @@ public: bool encrypted () const; - void add (KDM const &); + void add (DecryptedKDM const &); void write_xml ( Standard standard, diff --git a/src/decrypted_kdm.cc b/src/decrypted_kdm.cc new file mode 100644 index 00000000..7e9e146d --- /dev/null +++ b/src/decrypted_kdm.cc @@ -0,0 +1,276 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "decrypted_kdm.h" +#include "decrypted_kdm_key.h" +#include "encrypted_kdm.h" +#include "util.h" +#include "exceptions.h" +#include "cpl.h" +#include "mxf.h" +#include "signer.h" +#include "AS_DCP.h" +#include "KM_util.h" +#include "compose.hpp" +#include +#include +#include + +using std::list; +using std::string; +using std::stringstream; +using std::setw; +using std::setfill; +using std::hex; +using std::pair; +using boost::shared_ptr; +using namespace dcp; + +static void +put (uint8_t ** d, string s) +{ + memcpy (*d, s.c_str(), s.length()); + (*d) += s.length(); +} + +static void +put (uint8_t ** d, uint8_t const * s, int N) +{ + memcpy (*d, s, N); + (*d) += N; +} + +static void +put_uuid (uint8_t ** d, string id) +{ + id.erase (std::remove (id.begin(), id.end(), '-')); + for (int i = 0; i < 32; i += 2) { + stringstream s; + s << id[i] << id[i + 1]; + int h; + s >> hex >> h; + **d = h; + (*d)++; + } +} + +static string +get_uuid (unsigned char ** p) +{ + stringstream g; + + for (int i = 0; i < 16; ++i) { + g << setw(2) << setfill('0') << hex << static_cast (**p); + (*p)++; + if (i == 3 || i == 5 || i == 7 || i == 9) { + g << '-'; + } + } + + return g.str (); +} + +static string +get (uint8_t ** p, int N) +{ + string g; + for (int i = 0; i < N; ++i) { + g += **p; + (*p)++; + } + + return g; +} + +DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path private_key) +{ + /* Read the private key */ + + FILE* private_key_file = fopen_boost (private_key, "r"); + if (!private_key_file) { + throw FileError ("could not find RSA private key file", private_key, errno); + } + + RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0); + fclose (private_key_file); + if (!rsa) { + throw FileError ("could not read RSA private key file", private_key, errno); + } + + /* Use the private key to decrypt the keys */ + + list const encrypted_keys = kdm.keys (); + for (list::const_iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) { + + /* Decode the base-64-encoded cipher value from the KDM */ + unsigned char cipher_value[256]; + int const cipher_value_len = base64_decode (*i, cipher_value, sizeof (cipher_value)); + + /* Decrypt it */ + unsigned char * decrypted = new unsigned char[RSA_size(rsa)]; + int const decrypted_len = RSA_private_decrypt (cipher_value_len, cipher_value, decrypted, rsa, RSA_PKCS1_OAEP_PADDING); + if (decrypted_len == -1) { + delete[] decrypted; + throw MiscError (String::compose ("Could not decrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); + } + + unsigned char* p = decrypted; + switch (decrypted_len) { + case 134: + { + /* Inter-op */ + /* 0 is structure id (fixed sequence specified by standard) [16 bytes] */ + p += 16; + /* 16 is is signer thumbprint [20 bytes] */ + p += 20; + /* 36 is CPL id [16 bytes] */ + string const cpl_id = get_uuid (&p); + /* 52 is key id [16 bytes] */ + string const key_id = get_uuid (&p); + /* 68 is not-valid-before (a string) [25 bytes] */ + p += 25; + /* 93 is not-valid-after (a string) [25 bytes] */ + p += 25; + /* 118 is the key [ASDCP::KeyLen bytes] */ + _keys.push_back (DecryptedKDMKey ("", key_id, Key (p), cpl_id)); + break; + } + case 138: + { + /* SMPTE */ + /* 0 is structure id (fixed sequence specified by standard) [16 bytes] */ + p += 16; + /* 16 is is signer thumbprint [20 bytes] */ + p += 20; + /* 36 is CPL id [16 bytes] */ + string const cpl_id = get_uuid (&p); + /* 52 is key type [4 bytes] */ + string const key_type = get (&p, 4); + /* 56 is key id [16 bytes] */ + string const key_id = get_uuid (&p); + /* 72 is not-valid-before (a string) [25 bytes] */ + p += 25; + /* 97 is not-valid-after (a string) [25 bytes] */ + p += 25; + /* 112 is the key [ASDCP::KeyLen bytes] */ + _keys.push_back (DecryptedKDMKey (key_type, key_id, Key (p), cpl_id)); + break; + } + default: + assert (false); + } + + delete[] decrypted; + } + + RSA_free (rsa); +} + +DecryptedKDM::DecryptedKDM ( + boost::shared_ptr cpl, + LocalTime not_valid_before, + LocalTime not_valid_after, + string annotation_text, + string content_title_text, + string issue_date + ) + : _not_valid_before (not_valid_before) + , _not_valid_after (not_valid_after) + , _annotation_text (annotation_text) + , _content_title_text (content_title_text) + , _issue_date (issue_date) +{ + /* Create DecryptedKDMKey objects for each MXF asset */ + list > content = cpl->content (); + for (list >::iterator i = content.begin(); i != content.end(); ++i) { + /* XXX: do non-MXF assets need keys? */ + shared_ptr mxf = boost::dynamic_pointer_cast (*i); + if (mxf) { + _keys.push_back (DecryptedKDMKey (mxf->key_type(), mxf->key_id(), mxf->key().get (), cpl->id ())); + } + } +} + +EncryptedKDM +DecryptedKDM::encrypt (shared_ptr signer, shared_ptr recipient) const +{ + list > key_ids; + list keys; + for (list::const_iterator i = _keys.begin(); i != _keys.end(); ++i) { + + key_ids.push_back (make_pair (i->type(), i->id ())); + + /* XXX: SMPTE only */ + uint8_t block[138]; + uint8_t* p = block; + + /* Magic value specified by SMPTE S430-1-2006 */ + uint8_t structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab }; + put (&p, structure_id, 16); + + base64_decode (signer->certificates().leaf()->thumbprint (), p, 20); + p += 20; + + put_uuid (&p, i->cpl_id ()); + put (&p, i->type ()); + put_uuid (&p, i->id ()); + put (&p, _not_valid_before.as_string ()); + put (&p, _not_valid_after.as_string ()); + put (&p, i->key().value(), ASDCP::KeyLen); + + /* Encrypt using the projector's public key */ + RSA* rsa = recipient->public_key (); + unsigned char encrypted[RSA_size(rsa)]; + int const encrypted_len = RSA_public_encrypt (p - block, block, encrypted, rsa, RSA_PKCS1_OAEP_PADDING); + if (encrypted_len == -1) { + throw MiscError (String::compose ("Could not encrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); + } + + /* Lazy overallocation */ + char out[encrypted_len * 2]; + Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2); + int const N = strlen (out); + stringstream lines; + for (int i = 0; i < N; ++i) { + if (i > 0 && (i % 64) == 0) { + lines << "\n"; + } + lines << out[i]; + } + + keys.push_back (lines.str ()); + } + + string device_list_description = recipient->common_name (); + if (device_list_description.find (".") != string::npos) { + device_list_description = device_list_description.substr (device_list_description.find (".") + 1); + } + + return EncryptedKDM ( + signer, + recipient, + device_list_description, + _keys.front().cpl_id (), + _content_title_text, + _not_valid_before, + _not_valid_after, + key_ids, + keys + ); +} diff --git a/src/decrypted_kdm.h b/src/decrypted_kdm.h new file mode 100644 index 00000000..0bed341d --- /dev/null +++ b/src/decrypted_kdm.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "key.h" +#include "local_time.h" +#include "decrypted_kdm_key.h" +#include + +namespace dcp { + +class DecryptedKDMKey; +class EncryptedKDM; +class Signer; +class Certificate; +class CPL; + +class DecryptedKDM +{ +public: + /** @param kdm Encrypted KDM. + * @param private_key Private key file name. + */ + DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path private_key); + + DecryptedKDM ( + boost::shared_ptr cpl, + LocalTime not_valid_before, + LocalTime not_valid_after, + std::string annotation_text, + std::string content_title_text, + std::string issue_date + ); + + void add_key (std::string type, std::string id, Key key); + EncryptedKDM encrypt (boost::shared_ptr, boost::shared_ptr) const; + + std::list keys () const { + return _keys; + } + +private: + LocalTime _not_valid_before; + LocalTime _not_valid_after; + std::string _annotation_text; + std::string _content_title_text; + std::string _issue_date; + std::list _keys; +}; + +} diff --git a/test/utc_offset_to_string_test.cc b/src/decrypted_kdm_key.cc similarity index 56% rename from test/utc_offset_to_string_test.cc rename to src/decrypted_kdm_key.cc index 3b4e0b46..aac12539 100644 --- a/test/utc_offset_to_string_test.cc +++ b/src/decrypted_kdm_key.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,16 +17,12 @@ */ -#include -#include "metadata.h" -#include "util.h" +#include "decrypted_kdm_key.h" -/** Test dcp::utc_offset_to_string */ -BOOST_AUTO_TEST_CASE (utc_offset_to_string_test) +using namespace dcp; + +bool +dcp::operator== (dcp::DecryptedKDMKey const & a, dcp::DecryptedKDMKey const & b) { - BOOST_CHECK_EQUAL (dcp::utc_offset_to_string (30), "+00:30"); - BOOST_CHECK_EQUAL (dcp::utc_offset_to_string (60), "+01:00"); - BOOST_CHECK_EQUAL (dcp::utc_offset_to_string (61), "+01:01"); - BOOST_CHECK_EQUAL (dcp::utc_offset_to_string (7 * 60), "+07:00"); - BOOST_CHECK_EQUAL (dcp::utc_offset_to_string (-11 * 60), "-11:00"); + return a.type() == b.type() && a.id() == b.id() && a.key() == b.key() && a.cpl_id() == b.cpl_id(); } diff --git a/src/decrypted_kdm_key.h b/src/decrypted_kdm_key.h new file mode 100644 index 00000000..58448b7a --- /dev/null +++ b/src/decrypted_kdm_key.h @@ -0,0 +1,64 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef LIBDCP_DECRYPTED_KDM_KEY_H +#define LIBDCP_DECRYPTED_KDM_KEY_H + +#include "key.h" + +namespace dcp { + +class DecryptedKDMKey +{ +public: + DecryptedKDMKey (std::string type, std::string id, Key key, std::string cpl_id) + : _type (type) + , _id (id) + , _key (key) + , _cpl_id (cpl_id) + {} + + std::string type () const { + return _type; + } + + std::string id () const { + return _id; + } + + Key key () const { + return _key; + } + + std::string cpl_id () const { + return _cpl_id; + } + +private: + std::string _type; + std::string _id; + Key _key; + std::string _cpl_id; +}; + +bool operator== (DecryptedKDMKey const &, DecryptedKDMKey const &); + +} + +#endif diff --git a/src/encrypted_kdm.cc b/src/encrypted_kdm.cc new file mode 100644 index 00000000..5330490f --- /dev/null +++ b/src/encrypted_kdm.cc @@ -0,0 +1,591 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "encrypted_kdm.h" +#include "util.h" +#include "signer.h" +#include +#include +#include +#include +#include + +using std::list; +using std::string; +using std::map; +using std::pair; +using boost::shared_ptr; +using namespace dcp; + +namespace dcp { + +/** Namespace for classes used to hold our data; they are internal to this .cc file */ +namespace data { + +class Signer +{ +public: + Signer () {} + + Signer (shared_ptr node) + : x509_issuer_name (node->string_child ("X509IssuerName")) + , x509_serial_number (node->string_child ("X509SerialNumber")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name); + node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number); + } + + string x509_issuer_name; + string x509_serial_number; +}; + +class X509Data +{ +public: + X509Data () {} + + X509Data (boost::shared_ptr node) + : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial"))) + , x509_certificate (node->string_child ("X509Certificate")) + { + node->done (); + } + + void as_xml (xmlpp::Element* node) const + { + x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds")); + node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate); + } + + Signer x509_issuer_serial; + std::string x509_certificate; +}; + +class Reference +{ +public: + Reference () {} + + Reference (string u) + : uri (u) + {} + + Reference (shared_ptr node) + : uri (node->string_attribute ("URI")) + , digest_value (node->string_child ("DigestValue")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + node->set_attribute ("URI", uri); + node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + node->add_child("DigestValue", "ds")->add_child_text (digest_value); + } + + string uri; + string digest_value; +}; + +class SignedInfo +{ +public: + SignedInfo () + : authenticated_public ("#ID_AuthenticatedPublic") + , authenticated_private ("#ID_AuthenticatedPrivate") + {} + + SignedInfo (shared_ptr node) + { + list > references = node->node_children ("Reference"); + for (list >::const_iterator i = references.begin(); i != references.end(); ++i) { + if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") { + authenticated_public = Reference (*i); + } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") { + authenticated_private = Reference (*i); + } + + /* XXX: do something if we don't recognise the node */ + } + } + + void as_xml (xmlpp::Element* node) const + { + node->add_child ("CanonicalizationMethod", "ds")->set_attribute ( + "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + ); + + node->add_child ("SignatureMethod", "ds")->set_attribute ( + "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" + ); + + authenticated_public.as_xml (node->add_child ("Reference", "ds")); + authenticated_private.as_xml (node->add_child ("Reference", "ds")); + } + +private: + Reference authenticated_public; + Reference authenticated_private; +}; + +class Signature +{ +public: + Signature () {} + + Signature (shared_ptr node) + : signed_info (node->node_child ("SignedInfo")) + , signature_value (node->string_child ("SignatureValue")) + { + list > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data"); + for (list >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) { + x509_data.push_back (X509Data (*i)); + } + } + + void as_xml (xmlpp::Node* node) const + { + signed_info.as_xml (node->add_child ("SignedInfo", "ds")); + node->add_child("SignatureValue", "ds")->add_child_text (signature_value); + + xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds"); + for (std::list::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) { + i->as_xml (key_info_node->add_child ("X509Data", "ds")); + } + } + + SignedInfo signed_info; + string signature_value; + list x509_data; +}; + +class AuthenticatedPrivate +{ +public: + AuthenticatedPrivate () {} + + AuthenticatedPrivate (shared_ptr node) + { + list > encrypted_key_nodes = node->node_children ("EncryptedKey"); + for (list >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) { + encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue")); + } + } + + void as_xml (xmlpp::Element* node, map& references) const + { + references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate"); + + for (list::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) { + xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc"); + xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc"); + encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); + xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds"); + digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); + xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc"); + cipher_data->add_child("CipherValue", "enc")->add_child_text (*i); + } + } + + list encrypted_key; +}; + +class TypedKeyId +{ +public: + TypedKeyId () {} + + TypedKeyId (shared_ptr node) + : key_type (node->string_child ("KeyType")) + , key_id (node->string_child ("KeyId").substr (9)) + { + + } + + TypedKeyId (string type, string id) + : key_type (type) + , key_id (id) + {} + + void as_xml (xmlpp::Element* node) const + { + node->add_child("KeyType")->add_child_text (key_type); + node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id); + } + + string key_type; + string key_id; +}; + +class KeyIdList +{ +public: + KeyIdList () {} + + KeyIdList (shared_ptr node) + { + list > typed_key_id_nodes = node->node_children ("TypedKeyId"); + for (list >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) { + typed_key_id.push_back (TypedKeyId (*i)); + } + } + + void as_xml (xmlpp::Element* node) const + { + for (list::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) { + i->as_xml (node->add_child("TypedKeyId")); + } + } + + list typed_key_id; +}; + +class AuthorizedDeviceInfo +{ +public: + AuthorizedDeviceInfo () + : device_list_identifier (make_uuid ()) + /* Sometimes digital_cinema_tools uses this magic thumbprint instead of that from an actual + recipient certificate. KDMs delivered to City Screen appear to use the same thing. + */ + , certificate_thumbprint ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=") + {} + + AuthorizedDeviceInfo (shared_ptr node) + : device_list_identifier (node->string_child ("DeviceListIdentifier").substr (9)) + , device_list_description (node->string_child ("DeviceListDescription")) + , certificate_thumbprint (node->node_child("DeviceList")->string_child ("CertificateThumbprint")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier); + node->add_child ("DeviceListDescription")->add_child_text (device_list_description); + xmlpp::Element* device_list = node->add_child ("DeviceList"); + device_list->add_child("CertificateThumbprint")->add_child_text (certificate_thumbprint); + } + + string device_list_identifier; + string device_list_description; + string certificate_thumbprint; +}; + +class X509IssuerSerial +{ +public: + X509IssuerSerial () {} + + X509IssuerSerial (shared_ptr node) + : x509_issuer_name (node->string_child ("X509IssuerName")) + , x509_serial_number (node->string_child ("X509SerialNumber")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name); + node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number); + } + + string x509_issuer_name; + string x509_serial_number; +}; + +class Recipient +{ +public: + Recipient () {} + + Recipient (shared_ptr node) + : x509_issuer_serial (node->node_child ("X509IssuerSerial")) + , x509_subject_name (node->string_child ("X509SubjectName")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial")); + node->add_child("X509SubjectName")->add_child_text (x509_subject_name); + } + + X509IssuerSerial x509_issuer_serial; + string x509_subject_name; +}; + +class KDMRequiredExtensions +{ +public: + KDMRequiredExtensions () {} + + KDMRequiredExtensions (shared_ptr node) + : recipient (node->node_child ("Recipient")) + , composition_playlist_id (node->string_child ("CompositionPlaylistId").substr (9)) + , content_title_text (node->string_child ("ContentTitleText")) + , not_valid_before (node->string_child ("ContentKeysNotValidBefore")) + , not_valid_after (node->string_child ("ContentKeysNotValidAfter")) + , authorized_device_info (node->node_child ("AuthorizedDeviceInfo")) + , key_id_list (node->node_child ("KeyIdList")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM"); + + recipient.as_xml (node->add_child ("Recipient")); + node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id); + /* XXX: no ContentAuthenticator */ + node->add_child("ContentTitleText")->add_child_text (content_title_text); + node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ()); + node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ()); + authorized_device_info.as_xml (node->add_child ("AuthorizedDeviceInfo")); + key_id_list.as_xml (node->add_child ("KeyIdList")); + + xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList"); + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"); + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"); + } + + Recipient recipient; + string composition_playlist_id; + string content_title_text; + LocalTime not_valid_before; + LocalTime not_valid_after; + AuthorizedDeviceInfo authorized_device_info; + KeyIdList key_id_list; +}; + +class RequiredExtensions +{ +public: + RequiredExtensions () {} + + RequiredExtensions (shared_ptr node) + : kdm_required_extensions (node->node_child ("KDMRequiredExtensions")) + { + + } + + void as_xml (xmlpp::Element* node) const + { + kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions")); + } + + KDMRequiredExtensions kdm_required_extensions; +}; + +class AuthenticatedPublic +{ +public: + AuthenticatedPublic () + : message_id (make_uuid ()) + , issue_date (LocalTime().as_string ()) + {} + + AuthenticatedPublic (shared_ptr node) + : message_id (node->string_child ("MessageId").substr (9)) + , annotation_text (node->string_child ("AnnotationText")) + , issue_date (node->string_child ("IssueDate")) + , signer (node->node_child ("Signer")) + , required_extensions (node->node_child ("RequiredExtensions")) + { + + } + + void as_xml (xmlpp::Element* node, map& references) const + { + references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic"); + + node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id); + node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); + node->add_child("AnnotationText")->add_child_text (annotation_text); + node->add_child("IssueDate")->add_child_text (issue_date); + + signer.as_xml (node->add_child ("Signer")); + required_extensions.as_xml (node->add_child ("RequiredExtensions")); + + node->add_child ("NonCriticalExtensions"); + } + + string message_id; + string annotation_text; + string issue_date; + Signer signer; + RequiredExtensions required_extensions; +}; + +/** Class to describe our data. We use a class hierarchy as it's a bit nicer + * for XML data than a flat description. + */ +class EncryptedKDMData +{ +public: + EncryptedKDMData () + { + + } + + EncryptedKDMData (shared_ptr node) + : authenticated_public (node->node_child ("AuthenticatedPublic")) + , authenticated_private (node->node_child ("AuthenticatedPrivate")) + , signature (node->node_child ("Signature")) + { + + } + + shared_ptr as_xml () const + { + shared_ptr document (new xmlpp::Document ()); + xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM"); + root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); + root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); + map references; + authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references); + authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references); + signature.as_xml (root->add_child ("Signature", "ds")); + + for (map::const_iterator i = references.begin(); i != references.end(); ++i) { + xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ()); + } + + return document; + } + + AuthenticatedPublic authenticated_public; + AuthenticatedPrivate authenticated_private; + Signature signature; +}; + +} +} + +EncryptedKDM::EncryptedKDM (boost::filesystem::path file) + : _data (new data::EncryptedKDMData (shared_ptr (new cxml::Document ("DCinemaSecurityMessage", file)))) +{ + +} + +EncryptedKDM::EncryptedKDM ( + shared_ptr signer, + shared_ptr recipient, + string device_list_description, + string cpl_id, + string content_title_text, + LocalTime not_valid_before, + LocalTime not_valid_after, + list > key_ids, + list keys + ) + : _data (new data::EncryptedKDMData) +{ + /* Fill our XML-ish description in with the juicy bits that the caller has given */ + + data::AuthenticatedPublic& aup = _data->authenticated_public; + aup.signer.x509_issuer_name = signer->certificates().leaf()->issuer (); + aup.signer.x509_serial_number = signer->certificates().leaf()->serial (); + + data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions; + kre.recipient.x509_issuer_serial.x509_issuer_name = recipient->issuer (); + kre.recipient.x509_issuer_serial.x509_serial_number = recipient->serial (); + kre.recipient.x509_subject_name = recipient->subject (); + kre.authorized_device_info.device_list_description = device_list_description; + kre.composition_playlist_id = cpl_id; + kre.content_title_text = content_title_text; + kre.not_valid_before = not_valid_before; + kre.not_valid_after = not_valid_after; + + for (list >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) { + kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second)); + } + + _data->authenticated_private.encrypted_key = keys; + + /* Read the XML so far and sign it */ + shared_ptr doc = _data->as_xml (); + xmlpp::Node::NodeList children = doc->get_root_node()->get_children (); + for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) { + if ((*i)->get_name() == "Signature") { + signer->add_signature_value (*i, "ds"); + } + } + + /* Read the bits that add_signature_value did back into our variables */ + shared_ptr signed_doc (new cxml::Node (doc->get_root_node ())); + _data->signature = data::Signature (signed_doc->node_child ("Signature")); +} + +EncryptedKDM::EncryptedKDM (EncryptedKDM const & other) + : _data (new data::EncryptedKDMData (*other._data)) +{ + +} + +EncryptedKDM & +EncryptedKDM::operator= (EncryptedKDM const & other) +{ + if (this == &other) { + return *this; + } + + delete _data; + _data = new data::EncryptedKDMData (*other._data); + return *this; +} + +EncryptedKDM::~EncryptedKDM () +{ + delete _data; +} + +void +EncryptedKDM::as_xml (boost::filesystem::path path) const +{ + FILE* f = fopen_boost (path, "w"); + string const x = as_xml (); + fwrite (x.c_str(), 1, x.length(), f); + fclose (f); +} + +string +EncryptedKDM::as_xml () const +{ + xmlpp::Document document; + xmlpp::Element* root = document.create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM"); + root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); + root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); + + return _data->as_xml()->write_to_string ("UTF-8"); +} + +list +EncryptedKDM::keys () const +{ + return _data->authenticated_private.encrypted_key; +} diff --git a/src/encrypted_kdm.h b/src/encrypted_kdm.h new file mode 100644 index 00000000..8d13a25a --- /dev/null +++ b/src/encrypted_kdm.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "local_time.h" +#include +#include + +namespace cxml { + class Node; +} + +namespace dcp { + +namespace data { + class EncryptedKDMData; +} + +class Signer; +class Certificate; + +class EncryptedKDM +{ +public: + /** Read a KDM from an XML file */ + EncryptedKDM (boost::filesystem::path file); + + /** Construct an EncryptedKDM from a set of details */ + EncryptedKDM ( + boost::shared_ptr signer, + boost::shared_ptr recipient, + std::string device_list_description, + std::string cpl_id, + std::string cpl_content_title_text, + LocalTime _not_valid_before, + LocalTime _not_valid_after, + std::list > key_ids, + std::list keys + ); + + EncryptedKDM (EncryptedKDM const & kdm); + EncryptedKDM & operator= (EncryptedKDM const &); + ~EncryptedKDM (); + + void as_xml (boost::filesystem::path) const; + std::string as_xml () const; + + std::list keys () const; + +private: + data::EncryptedKDMData* _data; +}; + +} diff --git a/src/exceptions.cc b/src/exceptions.cc index 7cfcd699..48da4fcd 100644 --- a/src/exceptions.cc +++ b/src/exceptions.cc @@ -41,3 +41,10 @@ UnresolvedRefError::UnresolvedRefError (string id) } +TimeFormatError::TimeFormatError (string bad_time) + : _message (String::compose ("Bad time string %1", bad_time)) +{ + +} + + diff --git a/src/exceptions.h b/src/exceptions.h index bc5e83d1..7446e352 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -146,7 +146,25 @@ public: private: std::string _message; }; - + +/** @class TimeFormatError + * @brief A an error with a string passed to LocalTime. + */ +class TimeFormatError : public std::exception +{ +public: + TimeFormatError (std::string bad_time); + ~TimeFormatError () throw () {} + + /** @return error message */ + char const * what () const throw () { + return _message.c_str (); + } + +private: + std::string _message; +}; + } #endif diff --git a/src/file.cc b/src/file.cc index aabda166..f4a91add 100644 --- a/src/file.cc +++ b/src/file.cc @@ -23,6 +23,7 @@ #include "file.h" #include "util.h" +#include using namespace dcp; diff --git a/src/kdm.cc b/src/kdm.cc deleted file mode 100644 index 80521b81..00000000 --- a/src/kdm.cc +++ /dev/null @@ -1,477 +0,0 @@ -/* - Copyright (C) 2013-2014 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file src/kdm.cc - * @brief KDM and KDMKey classes. - */ - -#include "util.h" -#include "kdm.h" -#include "compose.hpp" -#include "exceptions.h" -#include "signer.h" -#include "cpl.h" -#include "mxf.h" -#include "kdm_smpte_xml.h" -#include "AS_DCP.h" -#include "KM_util.h" -#include -#include -#include -#include -#include -#include -#include - -using std::list; -using std::string; -using std::stringstream; -using std::map; -using std::hex; -using std::setw; -using std::setfill; -using std::cout; -using boost::shared_ptr; -using namespace dcp; - -KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key) -{ - /* Read the private key */ - - FILE* private_key_file = fopen_boost (private_key, "r"); - if (!private_key_file) { - throw FileError ("could not find RSA private key file", private_key, errno); - } - - RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0); - fclose (private_key_file); - if (!rsa) { - throw FileError ("could not read RSA private key file", private_key, errno); - } - - /* Read the encrypted keys from the XML */ - /* XXX: this should be reading more stuff from the XML to fill our member variables */ - - list encrypted_keys; - cxml::Document doc ("DCinemaSecurityMessage"); - doc.read_file (kdm.string ()); - - shared_ptr authenticated_private = doc.node_child ("AuthenticatedPrivate"); - list > encrypted_key_tags = authenticated_private->node_children ("EncryptedKey"); - for (list >::const_iterator i = encrypted_key_tags.begin(); i != encrypted_key_tags.end(); ++i) { - encrypted_keys.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue")); - } - - /* Use the private key to decrypt the keys */ - - for (list::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) { - - /* Decode the base-64-encoded cipher value from the KDM */ - unsigned char cipher_value[256]; - int const cipher_value_len = base64_decode (*i, cipher_value, sizeof (cipher_value)); - - /* Decrypt it */ - unsigned char* decrypted = new unsigned char[RSA_size(rsa)]; - int const decrypted_len = RSA_private_decrypt (cipher_value_len, cipher_value, decrypted, rsa, RSA_PKCS1_OAEP_PADDING); - if (decrypted_len == -1) { - delete[] decrypted; - throw MiscError (String::compose ("Could not decrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); - } - - _keys.push_back (KDMKey (decrypted, decrypted_len)); - delete[] decrypted; - } - - RSA_free (rsa); -} - -KDM::KDM ( - boost::shared_ptr cpl, - boost::shared_ptr signer, - boost::shared_ptr recipient_cert, - boost::posix_time::ptime not_valid_before, boost::posix_time::ptime not_valid_after, - string annotation_text, string issue_date - ) - : _id (make_uuid ()) - , _annotation_text (annotation_text) - , _issue_date (issue_date) - , _recipient_cert (recipient_cert) - , _cpl (cpl) - , _signer (signer) - , _not_valid_before (not_valid_before) - , _not_valid_after (not_valid_after) - , _device_list_identifier_id (make_uuid ()) -{ - /* Set up our KDMKey objects. This extracts Key objects from each MXF asset and - puts them (with other stuff) into KDMKey objects. - */ - list > content = cpl->content (); - for (list >::iterator i = content.begin(); i != content.end(); ++i) { - /* XXX: non-MXF assets? */ - shared_ptr mxf = boost::dynamic_pointer_cast (*i); - if (mxf) { - _keys.push_back ( - KDMKey ( - signer, cpl->id (), mxf->key_type (), mxf->key_id (), - not_valid_before, not_valid_after, mxf->key().get() - ) - ); - } - } -} - -void -KDM::authenticated_public (xmlpp::Element* node, map& references) const -{ - references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic"); - node->add_child("MessageId")->add_child_text ("urn:uuid:" + _id); - node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); - node->add_child("AnnotationText")->add_child_text (_annotation_text); - node->add_child("IssueDate")->add_child_text (_issue_date); - - /* Signer */ - xmlpp::Element* signer = node->add_child ("Signer"); - signer->add_child("X509IssuerName", "ds")->add_child_text (_signer->certificates().leaf()->issuer ()); - signer->add_child("X509SerialNumber", "ds")->add_child_text (_signer->certificates().leaf()->serial ()); - - /* Everything else is in RequiredExtensions/KDMRequiredExtensions */ - xmlpp::Element* kdm_required_extensions = node->add_child("RequiredExtensions")->add_child("KDMRequiredExtensions"); - kdm_required_extensions->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM"); - - /* Recipient */ - xmlpp::Element* recipient = kdm_required_extensions->add_child ("Recipient"); - xmlpp::Element* x509_issuer_serial = recipient->add_child ("X509IssuerSerial"); - x509_issuer_serial->add_child("X509IssuerName", "ds")->add_child_text (_recipient_cert->issuer ()); - x509_issuer_serial->add_child("X509SerialNumber", "ds")->add_child_text (_recipient_cert->serial ()); - recipient->add_child("X509SubjectName")->add_child_text (_recipient_cert->subject ()); - - kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + _cpl->id ()); - /* XXX: no ContentAuthenticator */ - kdm_required_extensions->add_child("ContentTitleText")->add_child_text (_cpl->content_title_text ()); - kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text (ptime_to_string (_not_valid_before)); - kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text (ptime_to_string (_not_valid_after)); - - /* AuthorizedDeviceInfo */ - xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo"); - authorized_device_info->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + _device_list_identifier_id); - string n = _recipient_cert->common_name (); - if (n.find (".") != string::npos) { - n = n.substr (n.find (".") + 1); - } - authorized_device_info->add_child ("DeviceListDescription")->add_child_text (n); - xmlpp::Element* device_list = authorized_device_info->add_child ("DeviceList"); - /* Sometimes digital_cinema_tools uses this magic thumbprint instead of that from an actual - recipient certificate. KDMs delivered to City Screen appear to use the same thing. - */ - device_list->add_child("CertificateThumbprint")->add_child_text ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); - - /* KeyIdList */ - xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList"); - list > content = _cpl->content (); - for (list >::iterator i = content.begin(); i != content.end(); ++i) { - /* XXX: non-MXF assets? */ - shared_ptr mxf = boost::dynamic_pointer_cast (*i); - if (mxf) { - xmlpp::Element* typed_key_id = key_id_list->add_child ("TypedKeyId"); - typed_key_id->add_child("KeyType")->add_child_text (mxf->key_type ()); - typed_key_id->add_child("KeyId")->add_child_text ("urn:uuid:" + mxf->key_id ()); - } - } - - /* ForensicMarkFlagList */ - xmlpp::Element* forensic_mark_flag_list = kdm_required_extensions->add_child ("ForensicMarkFlagList"); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"); - - node->add_child ("NonCriticalExtensions"); -} - -void -KDM::authenticated_private (xmlpp::Element* node, map& references) const -{ - references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate"); - - for (list::const_iterator i = _keys.begin(); i != _keys.end(); ++i) { - xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc"); - xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc"); - encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); - xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds"); - digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); - xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc"); - cipher_data->add_child("CipherValue", "enc")->add_child_text (i->encrypted_base64 (_recipient_cert)); - } -} - -void -KDM::signature (xmlpp::Element* node, map const & references) const -{ - xmlpp::Element* signed_info = node->add_child ("SignedInfo", "ds"); - signed_info->add_child ("CanonicalizationMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"); - signed_info->add_child ("SignatureMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - - for (map::const_iterator i = references.begin(); i != references.end(); ++i) { - xmlpp::Element* reference = signed_info->add_child ("Reference", "ds"); - reference->set_attribute ("URI", "#" + i->first); - reference->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); - reference->add_child("DigestValue", "ds")->add_child_text (""); - } - - node->add_child("SignatureValue", "ds")->add_child_text (""); - node->add_child("KeyInfo", "ds"); -} - -void -KDM::as_xml (boost::filesystem::path path) const -{ - FILE* f = fopen_boost (path, "w"); - string const x = as_xml (); - fwrite (x.c_str(), 1, x.length(), f); - fclose (f); -} - -string -KDM::as_xml () const -{ - xmlpp::Document document; - xmlpp::Element* root = document.create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM"); - root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); - root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); - - map references; - authenticated_public (root->add_child ("AuthenticatedPublic"), references); - authenticated_private (root->add_child ("AuthenticatedPrivate"), references); - - xmlpp::Element* signature_node = root->add_child ("Signature", "ds"); - signature (signature_node, references); - - for (map::const_iterator i = references.begin(); i != references.end(); ++i) { - xmlAddID (0, document.cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ()); - } - - _signer->add_signature_value (signature_node, "ds"); - - /* This must *not* be the _formatted version, otherwise the signature - will be wrong. - */ - return document.write_to_string ("UTF-8"); -} - -KDMKey::KDMKey ( - boost::shared_ptr signer, - string cpl_id, - string key_type, - string key_id, - boost::posix_time::ptime from, - boost::posix_time::ptime until, - Key key - ) - : _cpl_id (cpl_id) - , _key_type (key_type) - , _key_id (key_id) - , _not_valid_before (ptime_to_string (from)) - , _not_valid_after (ptime_to_string (until)) - , _key (key) -{ - base64_decode (signer->certificates().leaf()->thumbprint (), _signer_thumbprint, 20); -} - -KDMKey::KDMKey (uint8_t const * raw, int len) -{ - switch (len) { - case 134: - /* interop */ - /* [0-15] is structure id (fixed sequence specified by standard) */ - raw += 16; - get (_signer_thumbprint, &raw, 20); - _cpl_id = get_uuid (&raw); - _key_id = get_uuid (&raw); - _not_valid_before = get (&raw, 25); - _not_valid_after = get (&raw, 25); - _key = Key (raw); - break; - case 138: - /* SMPTE */ - /* [0-15] is structure id (fixed sequence specified by standard) */ - raw += 16; - get (_signer_thumbprint, &raw, 20); - _cpl_id = get_uuid (&raw); - _key_type = get (&raw, 4); - _key_id = get_uuid (&raw); - _not_valid_before = get (&raw, 25); - _not_valid_after = get (&raw, 25); - _key = Key (raw); - break; - default: - assert (false); - } -} - -KDMKey::KDMKey (KDMKey const & other) - : _cpl_id (other._cpl_id) - , _key_type (other._key_type) - , _key_id (other._key_id) - , _not_valid_before (other._not_valid_before) - , _not_valid_after (other._not_valid_after) - , _key (other._key) -{ - memcpy (_signer_thumbprint, other._signer_thumbprint, 20); -} - -KDMKey & -KDMKey::operator= (KDMKey const & other) -{ - if (&other == this) { - return *this; - } - - _cpl_id = other._cpl_id; - _key_type = other._key_type; - _key_id = other._key_id; - _not_valid_before = other._not_valid_before; - _not_valid_after = other._not_valid_after; - _key = other._key; - memcpy (_signer_thumbprint, other._signer_thumbprint, 20); - - return *this; -} - -string -KDMKey::encrypted_base64 (boost::shared_ptr recipient_cert) const -{ - assert (_key_type.length() == 4); - assert (_not_valid_before.length() == 25); - assert (_not_valid_after.length() == 25); - - /* XXX: SMPTE only */ - uint8_t block[138]; - uint8_t* p = block; - - /* Magic value specified by SMPTE S430-1-2006 */ - uint8_t structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab }; - put (&p, structure_id, 16); - put (&p, _signer_thumbprint, 20); - put_uuid (&p, _cpl_id); - put (&p, _key_type); - put_uuid (&p, _key_id); - put (&p, _not_valid_before); - put (&p, _not_valid_after); - put (&p, _key.value(), ASDCP::KeyLen); - - /* Encrypt using the projector's public key */ - RSA* rsa = recipient_cert->public_key (); - unsigned char encrypted[RSA_size(rsa)]; - int const encrypted_len = RSA_public_encrypt (p - block, block, encrypted, rsa, RSA_PKCS1_OAEP_PADDING); - if (encrypted_len == -1) { - throw MiscError (String::compose ("Could not encrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); - } - - /* Lazy overallocation */ - char out[encrypted_len * 2]; - Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2); - int const N = strlen (out); - stringstream lines; - for (int i = 0; i < N; ++i) { - if (i > 0 && (i % 64) == 0) { - lines << "\n"; - } - lines << out[i]; - } - - return lines.str (); -} - -string -KDMKey::get (uint8_t const ** p, int N) const -{ - string g; - for (int i = 0; i < N; ++i) { - g += **p; - (*p)++; - } - - return g; -} - -void -KDMKey::get (uint8_t* o, uint8_t const ** p, int N) const -{ - memcpy (o, *p, N); - *p += N; -} - -string -KDMKey::get_uuid (unsigned char const ** p) const -{ - stringstream g; - - for (int i = 0; i < 16; ++i) { - g << setw(2) << setfill('0') << hex << static_cast (**p); - (*p)++; - if (i == 3 || i == 5 || i == 7 || i == 9) { - g << '-'; - } - } - - return g.str (); -} - -void -KDMKey::put (uint8_t ** d, uint8_t const * s, int N) const -{ - memcpy (*d, s, N); - (*d) += N; -} - -void -KDMKey::put (uint8_t ** d, string s) const -{ - memcpy (*d, s.c_str(), s.length()); - (*d) += s.length(); -} - -void -KDMKey::put_uuid (uint8_t ** d, string id) const -{ - id.erase (std::remove (id.begin(), id.end(), '-')); - for (int i = 0; i < 32; i += 2) { - stringstream s; - s << id[i] << id[i + 1]; - int h; - s >> hex >> h; - **d = h; - (*d)++; - } -} - -bool -dcp::operator== (dcp::KDMKey const & a, dcp::KDMKey const & b) -{ - if (memcmp (a._signer_thumbprint, b._signer_thumbprint, 20) != 0) { - return false; - } - - return ( - a._cpl_id == b._cpl_id && - a._key_type == b._key_type && - a._key_id == b._key_id && - a._not_valid_before == b._not_valid_before && - a._not_valid_after == b._not_valid_after && - a._key == b._key - ); -} diff --git a/src/kdm.h b/src/kdm.h deleted file mode 100644 index 2a340c35..00000000 --- a/src/kdm.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - Copyright (C) 2013-2014 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file src/kdm.h - * @brief KDM and KDMKey classes. - */ - -#ifndef LIBDCP_KDM_H -#define LIBDCP_KDM_H - -#include "key.h" -#include "metadata.h" -#include -#include -#include - -class kdm_key_test; - -namespace xmlpp { - class Element; - class Attribute; -} - -namespace dcp { - -namespace xml { - class DCinemaSecurityMessage; -} - -class Signer; -class Certificate; -class CPL; - -/** @class KDMKey - * @brief A single key (and associated metadata) for encrypting or decrypting an MXF. - * - * One or more of these are delivered (themselves encrypted) in a KDM. The following - * data is collected into a block: - * - * A structure ID (a magic value specified by the standard) - * The thumbprint of the KDM signer's certificate. - * The CPL ID. - * The key ID. - * Validity start and end times. - * The key itself - * - * This data block is then encrypted using the projector's public key, so that - * only the target projector can decrypt block. - */ -class KDMKey -{ -public: - /** Create a KDMKey from the raw block that is encrypted in the KDM's CipherData. - * @param raw Pointer to data block (134 bytes for interop, 138 bytes for SMPTE). - * @param len Length of the data block in bytes. - */ - KDMKey (uint8_t const * raw, int len); - - /** Create a KDMKey from its constituent parts. - * @param signer Signer for the KDM. - * @param cpl_id ID of the CPL that the KDM is for. - * @param key_type Type of data that this key is for (MDIK for image, MDAK for audio, ...) - * @param key_id ID of this key. - * @param from Valid-from time. - * @param until Valid-until time. - * @param key The key itself. - */ - KDMKey ( - boost::shared_ptr signer, - std::string cpl_id, std::string key_type, std::string key_id, boost::posix_time::ptime from, boost::posix_time::ptime until, Key key - ); - - KDMKey (KDMKey const &); - - KDMKey& operator= (KDMKey const &); - - /** @return ID of the CPL that the KDM is for */ - std::string cpl_id () const { - return _cpl_id; - } - - /** @return ID of the key */ - std::string key_id () const { - return _key_id; - } - - /** @return start of the validity period as a string */ - std::string not_valid_before () const { - return _not_valid_before; - } - - /** @return end of the validity period as a string */ - std::string not_valid_after () const { - return _not_valid_after; - } - - /** @return the key itself */ - Key key () const { - return _key; - } - - /** @param cert Cerfificate. - * @return The data block encrypted with a certificate's public key and converted to base 64. - */ - std::string encrypted_base64 (boost::shared_ptr cert) const; - -private: - friend class ::kdm_key_test; - - void get (uint8_t *, uint8_t const **, int) const; - std::string get (uint8_t const **, int) const; - std::string get_uuid (uint8_t const **) const; - void put (uint8_t **, uint8_t const *, int) const; - void put (uint8_t **, std::string) const; - void put_uuid (uint8_t **, std::string) const; - - friend bool operator== (KDMKey const &, KDMKey const &); - - uint8_t _signer_thumbprint[20]; - std::string _cpl_id; - std::string _key_type; - std::string _key_id; - std::string _not_valid_before; - std::string _not_valid_after; - Key _key; -}; - -/** @class KDM - * @brief A class representing a Key Delivery Message (KDM). - * - * A KDM wraps one or more content keys (which we wrap into KDMKey objects) and various - * other metadata. This class can read and decrypt existing KDMs (provided you have - * the private key that the KDM was targeted at). It can also create new KDMs for - * a given CPL. - */ -class KDM -{ -public: - /** Load and decrypt a KDM. After this constructor the KDMKeys can be read - * and used to decrypt MXFs. - * - * @param kdm KDM file name. - * @param private_key Private key file name. - */ - KDM (boost::filesystem::path kdm, boost::filesystem::path private_key); - - /** Create a new KDM. - * @param cpl CPL that the KDM is for. - * @param signer Certificate chain to sign the KDM with. - * @param recipient_cert Certificate of the projector that this KDM is targeted at. - * @param not_valid_before Start of validity period. - * @param not_valid_after End of validity period. - * @param annotation_text Text for the <AnnotationText> node. - * @param issue_date Text for the <IssueDate> node. - */ - KDM ( - boost::shared_ptr cpl, boost::shared_ptr signer, boost::shared_ptr recipient_cert, - boost::posix_time::ptime not_valid_before, boost::posix_time::ptime not_valid_after, - std::string annotation_text, std::string issue_date - ); - - KDM (KDM const &); - KDM & operator= (KDM const &); - - /** @return The unencrypted content keys from this KDM */ - std::list keys () const { - return _keys; - } - - /** Write this KDM to a file. - * @param file File to write to. - */ - void as_xml (boost::filesystem::path file) const; - - /** Obtain this KDM as an XML string. - * @return XML string. - */ - std::string as_xml () const; - -private: - void authenticated_public (xmlpp::Element *, std::map& references) const; - void authenticated_private (xmlpp::Element *, std::map& references) const; - void signature (xmlpp::Element *, std::map const & references) const; - - /** Unencrypted MXF content keys */ - std::list _keys; - - /** AuthenticatedPublic/MessageId (without the urn:uuid: prefix) */ - std::string _id; - /** AuthenticatedPublic/AnnotationText */ - std::string _annotation_text; - /** AuthenticatedPublic/IssueDate */ - std::string _issue_date; - /** Certificate of recipient */ - boost::shared_ptr _recipient_cert; - /** CPL that this KDM is for, or 0 if we do not have a CPL object */ - boost::shared_ptr _cpl; - boost::shared_ptr _signer; - /** Start time for this KDM */ - boost::posix_time::ptime _not_valid_before; - /** End time for this KDM */ - boost::posix_time::ptime _not_valid_after; - /** KDMRequiredExtensions/AuthorizedDeviceInfo/DeviceListIdentifier */ - std::string _device_list_identifier_id; -}; - - -} - -#endif diff --git a/src/local_time.cc b/src/local_time.cc new file mode 100644 index 00000000..665d5d41 --- /dev/null +++ b/src/local_time.cc @@ -0,0 +1,102 @@ +/* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "local_time.h" +#include "exceptions.h" +#include +#include + +using std::string; +using boost::lexical_cast; +using namespace dcp; + +LocalTime::LocalTime () +{ + time_t now = time (0); + struct tm* tm = localtime (&now); + _year = tm->tm_year + 1900; + _month = tm->tm_mon + 1; + _day = tm->tm_mday + 1; + _hour = tm->tm_hour; + _minute = tm->tm_min; + _second = tm->tm_sec; + + int offset = 0; + +#ifdef LIBDCP_POSIX + offset = tm->tm_gmtoff / 60; +#else + TIME_ZONE_INFORMATION tz; + GetTimeZoneInformation (&tz); + offset = tz.Bias; +#endif + + bool const negative = offset < 0; + offset = negative ? -offset : offset; + + _tz_hour = offset / 60; + _tz_minute = offset % 60; + + if (negative) { + _tz_hour = -_tz_hour; + } +} + +/** @param s A string of the form 2013-01-05T18:06:59+04:00 */ +LocalTime::LocalTime (string s) +{ + /* 2013-01-05T18:06:59+04:00 */ + /* 0123456789012345678901234 */ + + if (s.length() < 25) { + throw TimeFormatError (s); + } + + /* Check incidental characters */ + if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' || s[16] != ':' || s[22] != ':') { + throw TimeFormatError (s); + } + + _year = lexical_cast (s.substr (0, 4)); + _month = lexical_cast (s.substr (5, 2)); + _day = lexical_cast (s.substr (8, 2)); + _hour = lexical_cast (s.substr (11, 2)); + _minute = lexical_cast (s.substr (14, 2)); + _second = lexical_cast (s.substr (17, 2)); + _tz_hour = lexical_cast (s.substr (20, 2)); + _tz_minute = lexical_cast (s.substr (23, 2)); + + if (s[19] == '-') { + _tz_hour = -_tz_hour; + } else if (s[19] != '+') { + throw TimeFormatError (s); + } +} + +string +LocalTime::as_string () const +{ + char buffer[32]; + snprintf ( + buffer, sizeof (buffer), + "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d", + _year, _month, _day, _hour, _minute, _second, (_tz_hour >= 0 ? "+" : "-"), abs (_tz_hour), _tz_minute + ); + return buffer; +} diff --git a/src/local_time.h b/src/local_time.h new file mode 100644 index 00000000..9c634fed --- /dev/null +++ b/src/local_time.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef LIBDCP_LOCAL_TIME_H +#define LIBDCP_LOCAL_TIME_H + +#include + +class local_time_test; + +namespace dcp { + +/** I tried to use boost for this, really I did */ +class LocalTime +{ +public: + LocalTime (); + LocalTime (std::string); + + std::string as_string () const; + +private: + friend class ::local_time_test; + + int _year; + int _month; + int _day; + int _hour; + int _minute; + int _second; + int _tz_hour; + int _tz_minute; +}; + +} + +#endif diff --git a/src/metadata.cc b/src/metadata.cc index e3382613..e2e19f3c 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -23,9 +23,7 @@ #include "metadata.h" #include "util.h" -#ifdef LIBDCP_WINDOWS -#include -#endif +#include "local_time.h" #include #include #include @@ -52,7 +50,5 @@ XMLMetadata::XMLMetadata () void XMLMetadata::set_issue_date_now () { - time_t now = time (0); - struct tm* tm = localtime (&now); - issue_date = tm_to_string (tm); + issue_date = LocalTime().as_string (); } diff --git a/src/mxf.cc b/src/mxf.cc index f8ca4fe5..3f8ab5d3 100644 --- a/src/mxf.cc +++ b/src/mxf.cc @@ -28,7 +28,6 @@ #include "util.h" #include "metadata.h" #include "exceptions.h" -#include "kdm.h" #include "compose.hpp" #include #include @@ -38,7 +37,6 @@ using std::string; using std::list; using std::pair; using boost::shared_ptr; -using boost::lexical_cast; using boost::dynamic_pointer_cast; using namespace dcp; diff --git a/src/reel.cc b/src/reel.cc index 4bdbcb5f..34b520b4 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -28,7 +28,8 @@ #include "reel_stereo_picture_asset.h" #include "reel_sound_asset.h" #include "reel_subtitle_asset.h" -#include "kdm.h" +#include "decrypted_kdm_key.h" +#include "decrypted_kdm.h" #include using std::string; @@ -133,15 +134,15 @@ Reel::encrypted () const } void -Reel::add (KDM const & kdm) +Reel::add (DecryptedKDM const & kdm) { - list keys = kdm.keys (); + list keys = kdm.keys (); - for (list::iterator i = keys.begin(); i != keys.end(); ++i) { - if (i->key_id() == _main_picture->key_id()) { + for (list::iterator i = keys.begin(); i != keys.end(); ++i) { + if (i->id() == _main_picture->key_id()) { _main_picture->mxf()->set_key (i->key ()); } - if (i->key_id() == _main_sound->key_id()) { + if (i->id() == _main_sound->key_id()) { _main_sound->mxf()->set_key (i->key ()); } } diff --git a/src/reel.h b/src/reel.h index 14def9c8..5bb19eb0 100644 --- a/src/reel.h +++ b/src/reel.h @@ -34,7 +34,7 @@ namespace cxml { namespace dcp { -class KDM; +class DecryptedKDM; class ReelAsset; class ReelPictureAsset; class ReelSoundAsset; @@ -81,7 +81,7 @@ public: bool equals (boost::shared_ptr other, EqualityOptions opt, boost::function notes) const; - void add (KDM const &); + void add (DecryptedKDM const &); void resolve_refs (std::list >); diff --git a/src/reel_picture_asset.cc b/src/reel_picture_asset.cc index d1910bb3..1a3d47b9 100644 --- a/src/reel_picture_asset.cc +++ b/src/reel_picture_asset.cc @@ -26,6 +26,7 @@ #include "picture_mxf.h" #include "compose.hpp" #include +#include using std::bad_cast; using std::string; diff --git a/src/rgb_xyz.cc b/src/rgb_xyz.cc index 92997441..72ee38c1 100644 --- a/src/rgb_xyz.cc +++ b/src/rgb_xyz.cc @@ -23,6 +23,7 @@ #include "gamma_lut.h" #include "image.h" #include "colour_matrix.h" +#include using std::min; using std::max; diff --git a/src/util.cc b/src/util.cc index 1e32fbc9..80c3756b 100644 --- a/src/util.cc +++ b/src/util.cc @@ -314,66 +314,6 @@ dcp::base64_decode (string const & in, unsigned char* out, int out_length) return N; } -/** Convert a struct tm to a string of the form - * 2014-01-26T21:39:00+01:00 - * @param tm struct tm. - * @return Time as a string. - */ -string -dcp::tm_to_string (struct tm* tm) -{ - char buffer[64]; - strftime (buffer, 64, "%Y-%m-%dT%H:%M:%S", tm); - - int offset = 0; - -#ifdef LIBDCP_POSIX - offset = tm->tm_gmtoff / 60; -#else - TIME_ZONE_INFORMATION tz; - GetTimeZoneInformation (&tz); - offset = tz.Bias; -#endif - - return string (buffer) + utc_offset_to_string (offset); -} - -/** @param b Offset from UTC to local time in minutes. - * @return string of the form e.g. -01:00. - */ -string -dcp::utc_offset_to_string (int b) -{ - bool const negative = (b < 0); - b = negative ? -b : b; - - int const hours = b / 60; - int const minutes = b % 60; - - stringstream o; - if (negative) { - o << "-"; - } else { - o << "+"; - } - - o << setw(2) << setfill('0') << hours << ":" << setw(2) << setfill('0') << minutes; - return o.str (); -} - -/** Convert a boost::posix_time::ptime to a string of the form - * 2014-01-26T21:39:00+01:00. - * @param t boost::posix_time::ptime. - * @return Time as a string. - */ -string -dcp::ptime_to_string (boost::posix_time::ptime t) -{ - struct tm t_tm = boost::posix_time::to_tm (t); - return tm_to_string (&t_tm); -} - - /** @param p Path to open. * @param t mode flags, as for fopen(3). * @return FILE pointer or 0 on error. diff --git a/src/util.h b/src/util.h index ca00ecb9..d9f69b95 100644 --- a/src/util.h +++ b/src/util.h @@ -27,7 +27,6 @@ #include "types.h" #include #include -#include #include #include #include @@ -87,9 +86,6 @@ extern void add_signer (xmlpp::Element* parent, CertificateChain const & certifi extern int base64_decode (std::string const & in, unsigned char* out, int out_length); extern boost::optional relative_to_root (boost::filesystem::path root, boost::filesystem::path file); -extern std::string tm_to_string (struct tm *); -extern std::string utc_offset_to_string (int); -extern std::string ptime_to_string (boost::posix_time::ptime); extern FILE * fopen_boost (boost::filesystem::path, std::string); template diff --git a/src/wscript b/src/wscript index c06ca1bd..a54a40d5 100644 --- a/src/wscript +++ b/src/wscript @@ -20,14 +20,17 @@ def build(bld): cpl.cc dcp.cc dcp_time.cc + decrypted_kdm.cc + decrypted_kdm_key.cc + encrypted_kdm.cc exceptions.cc file.cc font.cc gamma_lut.cc image.cc - kdm.cc key.cc load_font.cc + local_time.cc metadata.cc mono_picture_mxf.cc mono_picture_mxf_writer.cc @@ -71,10 +74,11 @@ def build(bld): content.h dcp.h dcp_time.h + decrypted_kdm.h + encrypted_kdm.h exceptions.h gamma_lut.h image.h - kdm.h key.h lut_cache.h metadata.h diff --git a/test/decryption_test.cc b/test/decryption_test.cc index 8dfeffa6..f80bb900 100644 --- a/test/decryption_test.cc +++ b/test/decryption_test.cc @@ -18,10 +18,11 @@ */ #include -#include "kdm.h" #include "dcp.h" #include "mono_picture_frame.h" #include "cpl.h" +#include "decrypted_kdm.h" +#include "encrypted_kdm.h" #include "argb_frame.h" #include "mono_picture_mxf.h" #include "reel_picture_asset.h" @@ -58,11 +59,11 @@ BOOST_AUTO_TEST_CASE (decryption_test) encrypted.read (); BOOST_CHECK_EQUAL (encrypted.encrypted (), true); - dcp::KDM kdm ( - "test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml", + dcp::DecryptedKDM kdm ( + dcp::EncryptedKDM ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"), "test/data/private.key" ); - + encrypted.add (kdm); shared_ptr plaintext_frame = get_frame (plaintext); @@ -78,8 +79,8 @@ BOOST_AUTO_TEST_CASE (decryption_test) /** Load in a KDM that didn't work at first */ BOOST_AUTO_TEST_CASE (failing_kdm_test) { - dcp::KDM kdm ( - "test/data/target.pem.crt.de5d4eba-e683-41ca-bdda-aa4ad96af3f4.kdm.xml", + dcp::DecryptedKDM kdm ( + dcp::EncryptedKDM ("test/data/target.pem.crt.de5d4eba-e683-41ca-bdda-aa4ad96af3f4.kdm.xml"), "test/data/private.key" ); } diff --git a/test/encryption_test.cc b/test/encryption_test.cc index 8fe34ddd..a4bc0600 100644 --- a/test/encryption_test.cc +++ b/test/encryption_test.cc @@ -17,7 +17,6 @@ */ -#include "kdm.h" #include "KM_util.h" #include "metadata.h" #include "certificates.h" @@ -35,6 +34,8 @@ #include "subtitle_content.h" #include "reel_mono_picture_asset.h" #include "reel_sound_asset.h" +#include "encrypted_kdm.h" +#include "decrypted_kdm.h" #include #include #include @@ -123,17 +124,16 @@ BOOST_AUTO_TEST_CASE (encryption_test) d.add (cpl); d.write_xml (dcp::SMPTE, xml_metadata, signer); - dcp::KDM kdm ( + dcp::DecryptedKDM kdm ( cpl, - signer, - signer->certificates().leaf(), - boost::posix_time::time_from_string ("2013-01-01 00:00:00"), - boost::posix_time::time_from_string ("2017-01-08 00:00:00"), + dcp::LocalTime ("2013-01-01T00:00:00+00:00"), + dcp::LocalTime ("2017-01-08T00:00:00+00:00"), "libdcp", + "test", "2012-07-17T04:45:18+00:00" ); - kdm.as_xml ("build/test/bar.kdm.xml"); + kdm.encrypt(signer, signer->certificates().leaf()).as_xml ("build/test/bar.kdm.xml"); int r = system ( "xmllint --path schema --nonet --noout --schema schema/SMPTE-430-1-2006-Amd-1-2009-KDM.xsd build/test/bar.kdm.xml " diff --git a/test/kdm_key_test.cc b/test/kdm_key_test.cc deleted file mode 100644 index 4a84fe3b..00000000 --- a/test/kdm_key_test.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include "kdm.h" - -BOOST_AUTO_TEST_CASE (kdm_key_test) -{ - uint8_t foo[138]; - memset (foo, 0, 138); - dcp::KDMKey kkey (foo, 138); - - uint8_t* raw = new uint8_t[16]; - uint8_t* p = raw; - kkey.put_uuid (&p, "5d51e8a1-b2a5-4da6-9b66-4615c3609440"); - BOOST_CHECK_EQUAL (raw[0], 0x5d); - BOOST_CHECK_EQUAL (raw[1], 0x51); - BOOST_CHECK_EQUAL (raw[2], 0xe8); - BOOST_CHECK_EQUAL (raw[3], 0xa1); - BOOST_CHECK_EQUAL (raw[4], 0xb2); - BOOST_CHECK_EQUAL (raw[5], 0xa5); - BOOST_CHECK_EQUAL (raw[6], 0x4d); - BOOST_CHECK_EQUAL (raw[7], 0xa6); - BOOST_CHECK_EQUAL (raw[8], 0x9b); - BOOST_CHECK_EQUAL (raw[9], 0x66); - BOOST_CHECK_EQUAL (raw[10], 0x46); - BOOST_CHECK_EQUAL (raw[11], 0x15); - BOOST_CHECK_EQUAL (raw[12], 0xc3); - BOOST_CHECK_EQUAL (raw[13], 0x60); - BOOST_CHECK_EQUAL (raw[14], 0x94); - BOOST_CHECK_EQUAL (raw[15], 0x40); - delete[] raw; -} diff --git a/test/kdm_test.cc b/test/kdm_test.cc index 5fff4fde..7de62f5a 100644 --- a/test/kdm_test.cc +++ b/test/kdm_test.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,44 +18,46 @@ */ #include -#include "kdm.h" +#include +#include "encrypted_kdm.h" +#include "decrypted_kdm.h" using std::list; +using std::stringstream; using boost::shared_ptr; BOOST_AUTO_TEST_CASE (kdm_test) { - dcp::KDM kdm ( - "test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml", + dcp::DecryptedKDM kdm ( + dcp::EncryptedKDM ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"), "test/data/private.key" ); - list keys = kdm.keys (); + list keys = kdm.keys (); BOOST_CHECK_EQUAL (keys.size(), 2); BOOST_CHECK_EQUAL (keys.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f"); - BOOST_CHECK_EQUAL (keys.front().key_id(), "4ac4f922-8239-4831-b23b-31426d0542c4"); - BOOST_CHECK_EQUAL (keys.front().not_valid_before(), "2013-07-06T20:04:58+00:00"); - BOOST_CHECK_EQUAL (keys.front().not_valid_after(), "2023-07-02T20:04:56+00:00"); + BOOST_CHECK_EQUAL (keys.front().id(), "4ac4f922-8239-4831-b23b-31426d0542c4"); BOOST_CHECK_EQUAL (keys.front().key().hex(), "8a2729c3e5b65c45d78305462104c3fb"); BOOST_CHECK_EQUAL (keys.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f"); - BOOST_CHECK_EQUAL (keys.back().key_id(), "73baf5de-e195-4542-ab28-8a465f7d4079"); - BOOST_CHECK_EQUAL (keys.back().not_valid_before(), "2013-07-06T20:04:58+00:00"); - BOOST_CHECK_EQUAL (keys.back().not_valid_after(), "2023-07-02T20:04:56+00:00"); + BOOST_CHECK_EQUAL (keys.back().id(), "73baf5de-e195-4542-ab28-8a465f7d4079"); BOOST_CHECK_EQUAL (keys.back().key().hex(), "5327fb7ec2e807bd57059615bf8a169d"); } /* Check that we can read in a KDM and then write it back out again the same */ BOOST_AUTO_TEST_CASE (kdm_passthrough_test) { - dcp::xml::DCinemaSecurityMessage kdm ( + dcp::EncryptedKDM kdm ( "test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml" ); - shared_ptr doc = kdm.as_xml (); - doc->write_to_file_formatted ("build/kdm.xml", "UTF-8"); + shared_ptr parser (new xmlpp::DomParser ()); + stringstream s; + s << kdm.as_xml (); + parser->parse_stream (s); + parser->get_document()->write_to_file_formatted ("build/kdm.xml", "UTF-8"); int const r = system ( "xmldiff -c test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml build/kdm.xml" ); diff --git a/test/local_time_test.cc b/test/local_time_test.cc new file mode 100644 index 00000000..b7f49a32 --- /dev/null +++ b/test/local_time_test.cc @@ -0,0 +1,63 @@ +/* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include "local_time.h" +#include "exceptions.h" + +/** Check that dcp::LocalTime works */ +BOOST_AUTO_TEST_CASE (local_time_test) +{ + /* Badly-formatted times */ + BOOST_CHECK_THROW (dcp::LocalTime (""), dcp::TimeFormatError); + BOOST_CHECK_THROW (dcp::LocalTime ("XXX"), dcp::TimeFormatError); + BOOST_CHECK_THROW (dcp::LocalTime ("2013-01-05T18:06:59+04:0"), dcp::TimeFormatError); + BOOST_CHECK_THROW (dcp::LocalTime ("2013-01-05T18:06:59X04:00"), dcp::TimeFormatError); + BOOST_CHECK_THROW (dcp::LocalTime ("2013-01-05T18-06:59+04:00"), dcp::TimeFormatError); + BOOST_CHECK_THROW (dcp::LocalTime ("2013!01-05T18:06:59+04:00"), dcp::TimeFormatError); + + /* Correctly-formatted */ + + { + dcp::LocalTime t ("2013-01-05T18:06:59+04:00"); + BOOST_CHECK_EQUAL (t._year, 2013); + BOOST_CHECK_EQUAL (t._month, 1); + BOOST_CHECK_EQUAL (t._day, 5); + BOOST_CHECK_EQUAL (t._hour, 18); + BOOST_CHECK_EQUAL (t._minute, 6); + BOOST_CHECK_EQUAL (t._second, 59); + BOOST_CHECK_EQUAL (t._tz_hour, 4); + BOOST_CHECK_EQUAL (t._tz_minute, 0); + BOOST_CHECK_EQUAL (t.as_string(), "2013-01-05T18:06:59+04:00"); + } + + { + dcp::LocalTime t ("2011-11-20T01:06:59-09:30"); + BOOST_CHECK_EQUAL (t._year, 2011); + BOOST_CHECK_EQUAL (t._month, 11); + BOOST_CHECK_EQUAL (t._day, 20); + BOOST_CHECK_EQUAL (t._hour, 1); + BOOST_CHECK_EQUAL (t._minute, 6); + BOOST_CHECK_EQUAL (t._second, 59); + BOOST_CHECK_EQUAL (t._tz_hour, -9); + BOOST_CHECK_EQUAL (t._tz_minute, 30); + BOOST_CHECK_EQUAL (t.as_string(), "2011-11-20T01:06:59-09:30"); + } +} + diff --git a/test/round_trip_test.cc b/test/round_trip_test.cc index e94d8c72..ef1f1f41 100644 --- a/test/round_trip_test.cc +++ b/test/round_trip_test.cc @@ -18,7 +18,8 @@ */ #include "certificates.h" -#include "kdm.h" +#include "decrypted_kdm.h" +#include "encrypted_kdm.h" #include "signer.h" #include "mono_picture_mxf.h" #include "sound_mxf.h" @@ -78,29 +79,28 @@ BOOST_AUTO_TEST_CASE (round_trip_test) cpl->add (reel); /* A KDM using our certificate chain's leaf key pair */ - dcp::KDM kdm_A ( + dcp::DecryptedKDM kdm_A ( cpl, - signer, - signer->certificates().leaf(), - boost::posix_time::time_from_string ("2013-01-01 00:00:00"), - boost::posix_time::time_from_string ("2013-01-08 00:00:00"), + dcp::LocalTime ("2013-01-01T00:00:00+00:00"), + dcp::LocalTime ("2013-01-08T00:00:00+00:00"), "libdcp", + "test", "2012-07-17T04:45:18+00:00" ); boost::filesystem::path const kdm_file = work_dir / "kdm.xml"; - kdm_A.as_xml (kdm_file); + kdm_A.encrypt(signer, signer->certificates().leaf()).as_xml (kdm_file); /* Reload the KDM, using our private key to decrypt it */ - dcp::KDM kdm_B (kdm_file, "build/test/signer/leaf.key"); + dcp::DecryptedKDM kdm_B (dcp::EncryptedKDM (kdm_file), "build/test/signer/leaf.key"); /* Check that the decrypted KDMKeys are the same as the ones we started with */ BOOST_CHECK_EQUAL (kdm_A.keys().size(), kdm_B.keys().size()); - list keys_A = kdm_A.keys (); - list keys_B = kdm_B.keys (); - list::const_iterator i = keys_A.begin(); - list::const_iterator j = keys_B.begin(); + list keys_A = kdm_A.keys (); + list keys_B = kdm_B.keys (); + list::const_iterator i = keys_A.begin(); + list::const_iterator j = keys_B.begin(); while (i != keys_A.end ()) { BOOST_CHECK (*i == *j); ++i; diff --git a/test/wscript b/test/wscript index 407765af..7e7122b1 100644 --- a/test/wscript +++ b/test/wscript @@ -29,14 +29,13 @@ def build(bld): decryption_test.cc encryption_test.cc frame_info_test.cc - kdm_key_test.cc + local_time_test.cc kdm_test.cc read_dcp_test.cc recovery_test.cc round_trip_test.cc subtitle_tests.cc test.cc - utc_offset_to_string_test.cc util_test.cc """ obj.target = 'tests' -- 2.30.2