X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fencrypted_kdm.cc;h=80c5e2ef9f9c4c4fa27eb534c8515c5daef59502;hb=839bc2d9d75f0d35a2c09d5e55e075e3ab1076b2;hp=be22ca5b00fd994d63e94d56c00ccc78078e73dc;hpb=d87f979ea98a19614f164a0d49fccc1be926e789;p=libdcp.git diff --git a/src/encrypted_kdm.cc b/src/encrypted_kdm.cc index be22ca5b..80c5e2ef 100644 --- a/src/encrypted_kdm.cc +++ b/src/encrypted_kdm.cc @@ -1,53 +1,70 @@ /* - Copyright (C) 2013-2014 Carl Hetherington + Copyright (C) 2013-2016 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 "signer.h" +#include "certificate_chain.h" #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 boost::optional; using namespace dcp; namespace dcp { -/** Namespace for classes used to hold our data; they are internal to this .cc file */ +/** 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) + + explicit 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 @@ -55,7 +72,7 @@ public: 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; }; @@ -64,8 +81,8 @@ class X509Data { public: X509Data () {} - - X509Data (boost::shared_ptr node) + + explicit X509Data (boost::shared_ptr node) : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial"))) , x509_certificate (node->string_child ("X509Certificate")) { @@ -77,34 +94,34 @@ public: 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) + + 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")) { } - + 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; }; @@ -117,7 +134,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) { @@ -140,22 +157,22 @@ public: 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) + explicit Signature (shared_ptr node) : signed_info (node->node_child ("SignedInfo")) , signature_value (node->string_child ("SignatureValue")) { @@ -169,7 +186,7 @@ public: { 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")); @@ -185,8 +202,8 @@ 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) { @@ -200,15 +217,19 @@ 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); } } - + list encrypted_key; }; @@ -216,10 +237,10 @@ 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"))) { } @@ -231,8 +252,13 @@ public: void as_xml (xmlpp::Element* node) const { - node->add_child("KeyType")->add_child_text (key_type); + xmlpp::Element* type = node->add_child("KeyType"); + type->add_child_text (key_type); node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id); + /* 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"); + } } string key_type; @@ -243,8 +269,8 @@ 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) { @@ -266,34 +292,40 @@ class AuthorizedDeviceInfo { public: AuthorizedDeviceInfo () {} - - 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")) - { + 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")) { + certificate_thumbprints.push_back (i->content ()); + } } 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); + if (device_list_description) { + node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get()); + } xmlpp::Element* device_list = node->add_child ("DeviceList"); - device_list->add_child("CertificateThumbprint")->add_child_text (certificate_thumbprint); + BOOST_FOREACH (string i, certificate_thumbprints) { + device_list->add_child("CertificateThumbprint")->add_child_text (i); + } } - + + /** DeviceListIdentifier without the urn:uuid: prefix */ string device_list_identifier; - string device_list_description; - string certificate_thumbprint; + boost::optional device_list_description; + std::list certificate_thumbprints; }; 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")) { @@ -314,8 +346,8 @@ 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")) { @@ -327,7 +359,7 @@ public: 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; }; @@ -336,10 +368,10 @@ 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")) @@ -352,30 +384,32 @@ public: 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); + node->add_child("ContentTitleText")->add_child_text (content_title_text); if (content_authenticator) { node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ()); } - 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")); + 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"); } - + Recipient recipient; string composition_playlist_id; boost::optional content_authenticator; string content_title_text; LocalTime not_valid_before; LocalTime not_valid_after; - AuthorizedDeviceInfo authorized_device_info; + boost::optional authorized_device_info; KeyIdList key_id_list; }; @@ -383,8 +417,8 @@ class RequiredExtensions { public: RequiredExtensions () {} - - RequiredExtensions (shared_ptr node) + + explicit RequiredExtensions (shared_ptr node) : kdm_required_extensions (node->node_child ("KDMRequiredExtensions")) { @@ -394,7 +428,7 @@ public: { kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions")); } - + KDMRequiredExtensions kdm_required_extensions; }; @@ -403,12 +437,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")) @@ -419,10 +455,12 @@ public: 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); + 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")); @@ -432,7 +470,7 @@ public: } string message_id; - string annotation_text; + optional annotation_text; string issue_date; Signer signer; RequiredExtensions required_extensions; @@ -448,13 +486,13 @@ 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")) { - + } shared_ptr as_xml () const @@ -483,18 +521,20 @@ public: } } -EncryptedKDM::EncryptedKDM (boost::filesystem::path file) - : _data (new data::EncryptedKDMData (shared_ptr (new cxml::Document ("DCinemaSecurityMessage", file)))) +EncryptedKDM::EncryptedKDM (string s) { - + shared_ptr doc (new cxml::Document ("DCinemaSecurityMessage")); + doc->read_string (s); + _data = new data::EncryptedKDMData (doc); } EncryptedKDM::EncryptedKDM ( - shared_ptr signer, - shared_ptr recipient, - string device_list_description, + shared_ptr signer, + Certificate recipient, + vector trusted_devices, string cpl_id, string content_title_text, + optional annotation_text, LocalTime not_valid_before, LocalTime not_valid_after, Formulation formulation, @@ -504,36 +544,48 @@ EncryptedKDM::EncryptedKDM ( : _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 (); + 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.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.composition_playlist_id = cpl_id; if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) { - kre.content_authenticator = signer->certificates().leaf()->thumbprint (); + kre.content_authenticator = signer->leaf().thumbprint (); } 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 = "urn:uuid:" + make_uuid (); - string n = recipient->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_thumbprint = "2jmj7l5rSw0yVb/vlWAYkK/YBwk="; - } else if (formulation == DCI_SPECIFIC) { - /* Use the recipient thumbprint */ - kre.authorized_device_info.certificate_thumbprint = recipient->thumbprint (); + 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 == 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()). + Waimea uses only the trusted devices here, too. + */ + BOOST_FOREACH (Certificate const & i, trusted_devices) { + kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ()); + } + } } for (list >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) { @@ -587,15 +639,10 @@ EncryptedKDM::as_xml (boost::filesystem::path path) const 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"); } @@ -604,3 +651,46 @@ EncryptedKDM::keys () const { return _data->authenticated_private.encrypted_key; } + +optional +EncryptedKDM::annotation_text () const +{ + return _data->authenticated_public.annotation_text; +} + +string +EncryptedKDM::content_title_text () const +{ + return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text; +} + +string +EncryptedKDM::cpl_id () const +{ + return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id; +} + +string +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; +} + +bool +dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b) +{ + /* Not exactly efficient... */ + return a.as_xml() == b.as_xml(); +}