X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fencrypted_kdm.cc;h=ca2a83460bcaa9c273869c786df14856d5de0689;hb=d39880eef211a296fa8ef4712cdef5945d08527c;hp=22bb86e4f46eadd13176e8610f6785922d522462;hpb=2a4eb72aec8515c20a19913f6fd3799eb7877857;p=libdcp.git diff --git a/src/encrypted_kdm.cc b/src/encrypted_kdm.cc index 22bb86e4..ca2a8346 100644 --- a/src/encrypted_kdm.cc +++ b/src/encrypted_kdm.cc @@ -1,38 +1,58 @@ /* - Copyright (C) 2013-2015 Carl Hetherington + Copyright (C) 2013-2018 Carl Hetherington - This program is free software; you can redistribute it and/or modify + This file is part of libdcp. + + libdcp 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, + libdcp 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. - + along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. */ #include "encrypted_kdm.h" #include "util.h" #include "certificate_chain.h" +#include "exceptions.h" +#include "compose.hpp" #include #include #include #include +#include #include #include +#include using std::list; using std::vector; using std::string; using std::map; using std::pair; -using boost::shared_ptr; +using std::shared_ptr; +using boost::optional; +using boost::starts_with; using namespace dcp; namespace dcp { @@ -45,7 +65,7 @@ class Signer public: Signer () {} - Signer (shared_ptr node) + explicit Signer (shared_ptr node) : x509_issuer_name (node->string_child ("X509IssuerName")) , x509_serial_number (node->string_child ("X509SerialNumber")) { @@ -67,7 +87,7 @@ class X509Data public: X509Data () {} - X509Data (boost::shared_ptr node) + explicit X509Data (std::shared_ptr node) : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial"))) , x509_certificate (node->string_child ("X509Certificate")) { @@ -89,11 +109,11 @@ class Reference public: Reference () {} - Reference (string u) + explicit Reference (string u) : uri (u) {} - Reference (shared_ptr node) + explicit Reference (shared_ptr node) : uri (node->string_attribute ("URI")) , digest_value (node->string_child ("DigestValue")) { @@ -119,7 +139,7 @@ public: , authenticated_private ("#ID_AuthenticatedPrivate") {} - SignedInfo (shared_ptr node) + explicit SignedInfo (shared_ptr node) { list > references = node->node_children ("Reference"); for (list >::const_iterator i = references.begin(); i != references.end(); ++i) { @@ -157,7 +177,7 @@ class Signature public: Signature () {} - Signature (shared_ptr node) + explicit Signature (shared_ptr node) : signed_info (node->node_child ("SignedInfo")) , signature_value (node->string_child ("SignatureValue")) { @@ -188,7 +208,7 @@ class AuthenticatedPrivate public: AuthenticatedPrivate () {} - AuthenticatedPrivate (shared_ptr node) + explicit 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) { @@ -202,9 +222,13 @@ public: for (list::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) { xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc"); + /* XXX: hack for testing with Dolby */ + encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "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"); + /* XXX: hack for testing with Dolby */ + digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "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); @@ -219,9 +243,9 @@ class TypedKeyId public: TypedKeyId () {} - TypedKeyId (shared_ptr node) + explicit TypedKeyId (shared_ptr node) : key_type (node->string_child ("KeyType")) - , key_id (node->string_child ("KeyId").substr (9)) + , key_id (remove_urn_uuid (node->string_child ("KeyId"))) { } @@ -239,6 +263,8 @@ public: /* XXX: this feels like a bit of a hack */ if (key_type == "MDEK") { type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type"); + } else { + type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); } } @@ -251,7 +277,7 @@ class KeyIdList public: KeyIdList () {} - KeyIdList (shared_ptr node) + explicit 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) { @@ -274,8 +300,8 @@ class AuthorizedDeviceInfo public: AuthorizedDeviceInfo () {} - AuthorizedDeviceInfo (shared_ptr node) - : device_list_identifier (node->string_child ("DeviceListIdentifier").substr (9)) + explicit AuthorizedDeviceInfo (shared_ptr node) + : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier"))) , device_list_description (node->optional_string_child ("DeviceListDescription")) { BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) { @@ -306,7 +332,7 @@ class X509IssuerSerial public: X509IssuerSerial () {} - X509IssuerSerial (shared_ptr node) + explicit X509IssuerSerial (shared_ptr node) : x509_issuer_name (node->string_child ("X509IssuerName")) , x509_serial_number (node->string_child ("X509SerialNumber")) { @@ -328,7 +354,7 @@ class Recipient public: Recipient () {} - Recipient (shared_ptr node) + explicit Recipient (shared_ptr node) : x509_issuer_serial (node->node_child ("X509IssuerSerial")) , x509_subject_name (node->string_child ("X509SubjectName")) { @@ -350,16 +376,34 @@ class KDMRequiredExtensions public: KDMRequiredExtensions () {} - KDMRequiredExtensions (shared_ptr node) + explicit KDMRequiredExtensions (shared_ptr node) : recipient (node->node_child ("Recipient")) - , composition_playlist_id (node->string_child ("CompositionPlaylistId").substr (9)) + , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId"))) , 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")) { - + disable_forensic_marking_picture = false; + disable_forensic_marking_audio = optional(); + if (node->optional_node_child("ForensicMarkFlagList")) { + BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) { + if (i->content() == picture_disable) { + disable_forensic_marking_picture = true; + } else if (starts_with(i->content(), audio_disable)) { + disable_forensic_marking_audio = 0; + string const above = audio_disable + "-above-channel-"; + if (starts_with(i->content(), above)) { + string above_number = i->content().substr(above.length()); + if (above_number == "") { + throw KDMFormatError("Badly-formatted ForensicMarkFlag"); + } + disable_forensic_marking_audio = atoi(above_number.c_str()); + } + } + } + } } void as_xml (xmlpp::Element* node) const @@ -374,12 +418,24 @@ public: } 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")); + if (authorized_device_info) { + 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"); + if (disable_forensic_marking_picture || disable_forensic_marking_audio) { + xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList"); + if (disable_forensic_marking_picture) { + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable); + } + if (disable_forensic_marking_audio) { + string mrkflg = audio_disable; + if (*disable_forensic_marking_audio > 0) { + mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio); + } + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg); + } + } } Recipient recipient; @@ -388,16 +444,25 @@ public: string content_title_text; LocalTime not_valid_before; LocalTime not_valid_after; - AuthorizedDeviceInfo authorized_device_info; + bool disable_forensic_marking_picture; + optional disable_forensic_marking_audio; + boost::optional authorized_device_info; KeyIdList key_id_list; + +private: + static const string picture_disable; + static const string audio_disable; }; +const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"; +const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"; + class RequiredExtensions { public: RequiredExtensions () {} - RequiredExtensions (shared_ptr node) + explicit RequiredExtensions (shared_ptr node) : kdm_required_extensions (node->node_child ("KDMRequiredExtensions")) { @@ -416,12 +481,14 @@ class AuthenticatedPublic public: AuthenticatedPublic () : message_id (make_uuid ()) + /* XXX: hack for Dolby to see if there must be a not-empty annotation text */ + , annotation_text ("none") , issue_date (LocalTime().as_string ()) {} - AuthenticatedPublic (shared_ptr node) - : message_id (node->string_child ("MessageId").substr (9)) - , annotation_text (node->string_child ("AnnotationText")) + explicit AuthenticatedPublic (shared_ptr node) + : message_id (remove_urn_uuid (node->string_child ("MessageId"))) + , annotation_text (node->optional_string_child ("AnnotationText")) , issue_date (node->string_child ("IssueDate")) , signer (node->node_child ("Signer")) , required_extensions (node->node_child ("RequiredExtensions")) @@ -435,7 +502,9 @@ public: 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); + if (annotation_text) { + node->add_child("AnnotationText")->add_child_text (annotation_text.get ()); + } node->add_child("IssueDate")->add_child_text (issue_date); signer.as_xml (node->add_child ("Signer")); @@ -445,7 +514,7 @@ public: } string message_id; - string annotation_text; + optional annotation_text; string issue_date; Signer signer; RequiredExtensions required_extensions; @@ -462,7 +531,7 @@ public: } - EncryptedKDMData (shared_ptr node) + explicit EncryptedKDMData (shared_ptr node) : authenticated_public (node->node_child ("AuthenticatedPublic")) , authenticated_private (node->node_child ("AuthenticatedPrivate")) , signature (node->node_child ("Signature")) @@ -485,6 +554,7 @@ public: xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ()); } + indent (document->get_root_node(), 0); return document; } @@ -498,21 +568,28 @@ public: EncryptedKDM::EncryptedKDM (string s) { - shared_ptr doc (new cxml::Document ("DCinemaSecurityMessage")); - doc->read_string (s); - _data = new data::EncryptedKDMData (doc); + try { + shared_ptr doc (new cxml::Document ("DCinemaSecurityMessage")); + doc->read_string (s); + _data = new data::EncryptedKDMData (doc); + } catch (xmlpp::parse_error& e) { + throw KDMFormatError (e.what ()); + } } +/** @param trusted_devices Trusted device thumbprints */ EncryptedKDM::EncryptedKDM ( shared_ptr signer, Certificate recipient, - vector trusted_devices, - string device_list_description, + vector trusted_devices, string cpl_id, string content_title_text, + optional annotation_text, LocalTime not_valid_before, LocalTime not_valid_after, Formulation formulation, + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio, list > key_ids, list keys ) @@ -520,15 +597,24 @@ EncryptedKDM::EncryptedKDM ( { /* Fill our XML-ish description in with the juicy bits that the caller has given */ + /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are: + * + * Type Trusted-device thumb ContentAuthenticator + * MODIFIED_TRANSITIONAL_1 assume-trust No + * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No + * DCI_ANY assume-trust Yes + * DCI_SPECIFIC as specified Yes + */ + data::AuthenticatedPublic& aup = _data->authenticated_public; aup.signer.x509_issuer_name = signer->leaf().issuer (); aup.signer.x509_serial_number = signer->leaf().serial (); + aup.annotation_text = annotation_text; 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; if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) { kre.content_authenticator = signer->leaf().thumbprint (); @@ -536,25 +622,41 @@ EncryptedKDM::EncryptedKDM ( kre.content_title_text = content_title_text; kre.not_valid_before = not_valid_before; kre.not_valid_after = not_valid_after; - kre.authorized_device_info.device_list_identifier = make_uuid (); - string n = recipient.subject_common_name (); - if (n.find (".") != string::npos) { - n = n.substr (n.find (".") + 1); - } - kre.authorized_device_info.device_list_description = n; - - if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) { - /* Use the "assume trust" thumbprint */ - kre.authorized_device_info.certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); - } else if (formulation == DCI_SPECIFIC) { - /* As I read the standard we should use the recipient - /and/ other trusted device thumbprints here. MJD - reports that this doesn't work with his setup; - a working KDM does not include the recipient's - thumbprint (recipient.thumbprint()). - */ - BOOST_FOREACH (Certificate const & i, trusted_devices) { - kre.authorized_device_info.certificate_thumbprints.push_back (i.thumbprint ()); + kre.disable_forensic_marking_picture = disable_forensic_marking_picture; + kre.disable_forensic_marking_audio = disable_forensic_marking_audio; + + if (formulation != MODIFIED_TRANSITIONAL_TEST) { + kre.authorized_device_info = data::AuthorizedDeviceInfo (); + kre.authorized_device_info->device_list_identifier = make_uuid (); + string n = recipient.subject_common_name (); + if (n.find (".") != string::npos) { + n = n.substr (n.find (".") + 1); + } + kre.authorized_device_info->device_list_description = n; + + if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) { + /* Use the "assume trust" thumbprint */ + kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); + } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) { + if (trusted_devices.empty ()) { + /* Fall back on the "assume trust" thumbprint so we + can generate "modified-transitional-1" KDMs + together with "multiple-modified-transitional-1" + KDMs in one go, and similarly for "dci-any" etc. + */ + kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); + } else { + /* As I read the standard we should use the + recipient /and/ other trusted device thumbprints + here. MJD reports that this doesn't work with + his setup; a working KDM does not include the + recipient's thumbprint (recipient.thumbprint()). + Waimea uses only the trusted devices here, too. + */ + BOOST_FOREACH (string i, trusted_devices) { + kre.authorized_device_info->certificate_thumbprints.push_back (i); + } + } } } @@ -569,7 +671,7 @@ EncryptedKDM::EncryptedKDM ( 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"); + signer->add_signature_value (dynamic_cast(*i), "ds", false); } } @@ -605,9 +707,15 @@ void EncryptedKDM::as_xml (boost::filesystem::path path) const { FILE* f = fopen_boost (path, "w"); + if (!f) { + throw FileError ("Could not open KDM file for writing", path, errno); + } string const x = as_xml (); - fwrite (x.c_str(), 1, x.length(), f); + size_t const written = fwrite (x.c_str(), 1, x.length(), f); fclose (f); + if (written != x.length()) { + throw FileError ("Could not write to KDM file", path, errno); + } } string @@ -623,6 +731,12 @@ EncryptedKDM::keys () const } string +EncryptedKDM::id () const +{ + return _data->authenticated_public.message_id; +} + +optional EncryptedKDM::annotation_text () const { return _data->authenticated_public.annotation_text; @@ -646,6 +760,35 @@ EncryptedKDM::issue_date () const return _data->authenticated_public.issue_date; } +LocalTime +EncryptedKDM::not_valid_before () const +{ + return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before; +} + +LocalTime +EncryptedKDM::not_valid_after () const +{ + return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after; +} + +string +EncryptedKDM::recipient_x509_subject_name () const +{ + return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name; +} + +CertificateChain +EncryptedKDM::signer_certificate_chain () const +{ + CertificateChain chain; + BOOST_FOREACH (data::X509Data const & i, _data->signature.x509_data) { + string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----"; + chain.add (Certificate(s)); + } + return chain; +} + bool dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b) {