diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/asset.cc | 4 | ||||
| -rw-r--r-- | src/asset.h | 12 | ||||
| -rw-r--r-- | src/certificates.cc | 199 | ||||
| -rw-r--r-- | src/certificates.h | 80 | ||||
| -rw-r--r-- | src/cpl.cc | 164 | ||||
| -rw-r--r-- | src/cpl.h | 15 | ||||
| -rw-r--r-- | src/crypt_chain.cc | 140 | ||||
| -rw-r--r-- | src/crypt_chain.h | 6 | ||||
| -rw-r--r-- | src/dcp.cc | 40 | ||||
| -rw-r--r-- | src/dcp.h | 9 | ||||
| -rw-r--r-- | src/dcp_time.cc | 11 | ||||
| -rw-r--r-- | src/dcp_time.h | 3 | ||||
| -rw-r--r-- | src/mxf_asset.cc | 53 | ||||
| -rw-r--r-- | src/mxf_asset.h | 17 | ||||
| -rw-r--r-- | src/picture_asset.cc | 29 | ||||
| -rw-r--r-- | src/picture_asset.h | 8 | ||||
| -rw-r--r-- | src/reel.cc | 1 | ||||
| -rw-r--r-- | src/reel.h | 4 | ||||
| -rw-r--r-- | src/sound_asset.cc | 29 | ||||
| -rw-r--r-- | src/sound_asset.h | 7 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 1 | ||||
| -rw-r--r-- | src/util.cc | 136 | ||||
| -rw-r--r-- | src/util.h | 11 | ||||
| -rw-r--r-- | src/wscript | 6 |
24 files changed, 932 insertions, 53 deletions
diff --git a/src/asset.cc b/src/asset.cc index 84bdd2bd..c566a1e5 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -24,8 +24,9 @@ #include <iostream> #include <fstream> #include <boost/filesystem.hpp> -#include <boost/function.hpp> #include <boost/lexical_cast.hpp> +#include <boost/function.hpp> +#include <libxml++/nodes/element.h> #include "AS_DCP.h" #include "KM_util.h" #include "asset.h" @@ -93,7 +94,6 @@ Asset::digest () const return _digest; } - bool Asset::equals (shared_ptr<const Asset> other, EqualityOptions, boost::function<void (NoteType, string)> note) const { diff --git a/src/asset.h b/src/asset.h index 3bc713a3..3dffa9ab 100644 --- a/src/asset.h +++ b/src/asset.h @@ -35,6 +35,10 @@ namespace ASDCP { class WriterInfo; } +namespace xmlpp { + class Element; +} + namespace libdcp { @@ -53,13 +57,13 @@ public: virtual ~Asset() {} - /** Write details of the asset to a CPL stream. - * @param s Stream. + /** Write details of the asset to a CPL AssetList node. + * @param p Parent node. */ virtual void write_to_cpl (xmlpp::Node *) const = 0; - /** Write details of the asset to a PKL stream. - * @param s Stream. + /** Write details of the asset to a PKL AssetList node. + * @param p Parent node. */ void write_to_pkl (xmlpp::Node *) const; diff --git a/src/certificates.cc b/src/certificates.cc new file mode 100644 index 00000000..372f8571 --- /dev/null +++ b/src/certificates.cc @@ -0,0 +1,199 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + 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 <sstream> +#include <vector> +#include <boost/algorithm/string.hpp> +#include <openssl/x509.h> +#include <openssl/ssl.h> +#include <openssl/asn1.h> +#include <libxml++/nodes/element.h> +#include "KM_util.h" +#include "certificates.h" +#include "exceptions.h" + +using std::list; +using std::string; +using std::stringstream; +using std::vector; +using boost::shared_ptr; +using namespace libdcp; + +/** @param c X509 certificate, which this object will take ownership of */ +Certificate::Certificate (X509* c) + : _certificate (c) +{ + +} + +Certificate::Certificate (string const & filename) + : _certificate (0) +{ + FILE* f = fopen (filename.c_str(), "r"); + if (!f) { + throw FileError ("could not open file", filename); + } + + if (!PEM_read_X509 (f, &_certificate, 0, 0)) { + throw MiscError ("could not read X509 certificate"); + } +} + +Certificate::~Certificate () +{ + X509_free (_certificate); +} + +string +Certificate::certificate () const +{ + assert (_certificate); + + BIO* bio = BIO_new (BIO_s_mem ()); + if (!bio) { + throw MiscError ("could not create memory BIO"); + } + + PEM_write_bio_X509 (bio, _certificate); + + string s; + char* data; + long int const data_length = BIO_get_mem_data (bio, &data); + for (long int i = 0; i < data_length; ++i) { + s += data[i]; + } + + BIO_free (bio); + + boost::replace_all (s, "-----BEGIN CERTIFICATE-----\n", ""); + boost::replace_all (s, "\n-----END CERTIFICATE-----\n", ""); + return s; +} + +string +Certificate::issuer () const +{ + assert (_certificate); + + X509_NAME* n = X509_get_issuer_name (_certificate); + assert (n); + + char b[256]; + X509_NAME_oneline (n, b, 256); + return b; +} + +string +Certificate::name_for_xml (string const & n) +{ + stringstream x; + + vector<string> p; + boost::split (p, n, boost::is_any_of ("/")); + for (vector<string>::const_reverse_iterator i = p.rbegin(); i != p.rend(); ++i) { + x << *i << ","; + } + + string s = x.str(); + boost::replace_all (s, "+", "\\+"); + + return s.substr(0, s.length() - 2); +} + +string +Certificate::subject () const +{ + assert (_certificate); + + X509_NAME* n = X509_get_subject_name (_certificate); + assert (n); + + char b[256]; + X509_NAME_oneline (n, b, 256); + return b; +} + +string +Certificate::serial () const +{ + assert (_certificate); + + ASN1_INTEGER* s = X509_get_serialNumber (_certificate); + assert (s); + + BIGNUM* b = ASN1_INTEGER_to_BN (s, 0); + char* c = BN_bn2dec (b); + BN_free (b); + + string st (c); + OPENSSL_free (c); + + return st; +} + +string +Certificate::thumbprint () const +{ + assert (_certificate); + + uint8_t buffer[8192]; + uint8_t* p = buffer; + i2d_X509_CINF (_certificate->cert_info, &p); + int const length = p - buffer; + if (length > 8192) { + throw MiscError ("buffer too small to generate thumbprint"); + } + + SHA_CTX sha; + SHA1_Init (&sha); + SHA1_Update (&sha, buffer, length); + uint8_t digest[20]; + SHA1_Final (digest, &sha); + + char digest_base64[64]; + return Kumu::base64encode (digest, 20, digest_base64, 64); +} + +shared_ptr<Certificate> +CertificateChain::root () const +{ + assert (!_certificates.empty()); + return _certificates.front (); +} + +shared_ptr<Certificate> +CertificateChain::leaf () const +{ + assert (_certificates.size() >= 2); + return _certificates.back (); +} + +list<shared_ptr<Certificate> > +CertificateChain::leaf_to_root () const +{ + list<shared_ptr<Certificate> > c = _certificates; + c.reverse (); + return c; +} + +void +CertificateChain::add (shared_ptr<Certificate> c) +{ + _certificates.push_back (c); +} diff --git a/src/certificates.h b/src/certificates.h new file mode 100644 index 00000000..e1a572ec --- /dev/null +++ b/src/certificates.h @@ -0,0 +1,80 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + 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_CERTIFICATES_H +#define LIBDCP_CERTIFICATES_H + +#include <string> +#include <list> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <openssl/x509.h> + +class certificates; + +namespace xmlpp { + class Element; +} + +namespace libdcp { + +class Certificate : public boost::noncopyable +{ +public: + Certificate () + : _certificate (0) + {} + + Certificate (std::string const &); + Certificate (X509 *); + ~Certificate (); + + std::string certificate () const; + std::string issuer () const; + std::string serial () const; + std::string subject () const; + + std::string thumbprint () const; + + static std::string name_for_xml (std::string const &); + +private: + X509* _certificate; +}; + +class CertificateChain +{ +public: + CertificateChain () {} + + void add (boost::shared_ptr<Certificate>); + + boost::shared_ptr<Certificate> root () const; + boost::shared_ptr<Certificate> leaf () const; + + std::list<boost::shared_ptr<Certificate> > leaf_to_root () const; + +private: + friend class ::certificates; + std::list<boost::shared_ptr<Certificate> > _certificates; +}; + +} + +#endif @@ -27,6 +27,7 @@ #include "parse/asset_map.h" #include "reel.h" #include "metadata.h" +#include "encryption.h" #include "exceptions.h" #include "compose.hpp" @@ -176,7 +177,7 @@ CPL::add_reel (shared_ptr<const Reel> reel) } void -CPL::write_xml (XMLMetadata const & metadata) const +CPL::write_xml (XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const { boost::filesystem::path p; p /= _directory; @@ -186,6 +187,11 @@ CPL::write_xml (XMLMetadata const & metadata) const xmlpp::Document doc; xmlpp::Element* root = doc.create_root_node ("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL"); + + if (crypt) { + root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); + } + root->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); root->add_child("AnnotationText")->add_child_text (_name); root->add_child("IssueDate")->add_child_text (metadata.issue_date); @@ -205,6 +211,10 @@ CPL::write_xml (XMLMetadata const & metadata) const (*i)->write_to_cpl (reel_list); } + if (crypt) { + sign (root, crypt->certificates, crypt->signer_key); + } + doc.write_to_file_formatted (p.string (), "UTF-8"); _digest = make_digest (p.string ()); @@ -300,3 +310,155 @@ CPL::equals (CPL const & other, EqualityOptions opt, boost::function<void (NoteT return true; } + +shared_ptr<xmlpp::Document> +CPL::make_kdm ( + CertificateChain const & certificates, + string const & signer_key, + shared_ptr<const Certificate> recipient_cert, + boost::posix_time::ptime from, + boost::posix_time::ptime until + ) const +{ + assert (recipient_cert); + + shared_ptr<xmlpp::Document> doc (new xmlpp::Document); + xmlpp::Element* root = doc->create_root_node ("DCinemaSecurityMessage"); + root->set_namespace_declaration ("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"); + + { + xmlpp::Element* authenticated_public = root->add_child("AuthenticatedPublic"); + authenticated_public->set_attribute("Id", "ID_AuthenticatedPublic"); + xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPublic", authenticated_public->get_attribute("Id")->cobj()); + + authenticated_public->add_child("MessageId")->add_child_text ("urn:uuid:" + make_uuid()); + authenticated_public->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); + authenticated_public->add_child("AnnotationText")->add_child_text (MXFMetadata::instance()->product_name); + authenticated_public->add_child("IssueDate")->add_child_text (MXFMetadata::instance()->issue_date); + + { + xmlpp::Element* signer = authenticated_public->add_child("Signer"); + signer->add_child("X509IssuerName", "ds")->add_child_text ( + Certificate::name_for_xml (recipient_cert->issuer()) + ); + signer->add_child("X509SerialNumber", "ds")->add_child_text ( + recipient_cert->serial() + ); + } + + { + xmlpp::Element* required_extensions = authenticated_public->add_child("RequiredExtensions"); + + { + xmlpp::Element* kdm_required_extensions = required_extensions->add_child("KDMRequiredExtensions"); + kdm_required_extensions->set_namespace_declaration ("http://www.smpte-ra.org/schemas/430-1/2006/KDM"); + { + xmlpp::Element* recipient = kdm_required_extensions->add_child("Recipient"); + { + xmlpp::Element* serial_element = recipient->add_child("X509IssuerSerial"); + serial_element->add_child("X509IssuerName", "ds")->add_child_text ( + Certificate::name_for_xml (recipient_cert->issuer()) + ); + serial_element->add_child("X509SerialNumber", "ds")->add_child_text ( + recipient_cert->serial() + ); + } + + recipient->add_child("X509SubjectName")->add_child_text (Certificate::name_for_xml (recipient_cert->subject())); + } + + kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text("urn:uuid:" + _uuid); + kdm_required_extensions->add_child("ContentTitleText")->add_child_text(_name); + kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text(certificates.leaf()->thumbprint()); + kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text("XXX"); + kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text("XXX"); + + { + xmlpp::Element* authorized_device_info = kdm_required_extensions->add_child("AuthorizedDeviceInfo"); + authorized_device_info->add_child("DeviceListIdentifier")->add_child_text("urn:uuid:" + make_uuid()); + authorized_device_info->add_child("DeviceListDescription")->add_child_text(recipient_cert->subject()); + { + xmlpp::Element* device_list = authorized_device_info->add_child("DeviceList"); + device_list->add_child("CertificateThumbprint")->add_child_text(recipient_cert->thumbprint()); + } + } + + { + xmlpp::Element* key_id_list = kdm_required_extensions->add_child("KeyIdList"); + list<shared_ptr<const Asset> > a = assets(); + for (list<shared_ptr<const Asset> >::iterator i = a.begin(); i != a.end(); ++i) { + /* XXX: non-MXF assets? */ + shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i); + if (mxf) { + mxf->add_typed_key_id (key_id_list); + } + } + } + + { + 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" + ); + } + } + } + + authenticated_public->add_child("NonCriticalExtensions"); + } + + { + xmlpp::Element* authenticated_private = root->add_child("AuthenticatedPrivate"); + authenticated_private->set_attribute ("Id", "ID_AuthenticatedPrivate"); + xmlAddID (0, doc->cobj(), (const xmlChar *) "ID_AuthenticatedPrivate", authenticated_private->get_attribute("Id")->cobj()); + { + xmlpp::Element* encrypted_key = authenticated_private->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"); + encryption_method->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); + } + + xmlpp::Element* cipher_data = authenticated_private->add_child ("CipherData", "enc"); + cipher_data->add_child("CipherValue", "enc")->add_child_text("XXX"); + } + } + + /* XXX: x2 one for each mxf? */ + + { + xmlpp::Element* signature = root->add_child("Signature", "ds"); + + { + xmlpp::Element* signed_info = signature->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" + ); + { + xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); + reference->set_attribute("URI", "#ID_AuthenticatedPublic"); + reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + reference->add_child("DigestValue", "ds"); + } + + { + xmlpp::Element* reference = signed_info->add_child("Reference", "ds"); + reference->set_attribute("URI", "#ID_AuthenticatedPrivate"); + reference->add_child("DigestMethod", "ds")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + reference->add_child("DigestValue", "ds"); + } + } + + add_signature_value (signature, certificates, signer_key, "ds"); + } + + return doc; +} @@ -20,8 +20,10 @@ #include <list> #include <boost/shared_ptr.hpp> #include <boost/function.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> #include <libxml++/libxml++.h> #include "types.h" +#include "certificates.h" namespace libdcp { @@ -32,7 +34,8 @@ namespace parse { class Asset; class Reel; class XMLMetadata; - + class Encryption; + /** @brief A CPL within a DCP */ class CPL { @@ -74,9 +77,17 @@ public: bool equals (CPL const & other, EqualityOptions options, boost::function<void (NoteType, std::string)> note) const; - void write_xml (XMLMetadata const &) const; + void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption>) const; void write_to_assetmap (xmlpp::Node *) const; void write_to_pkl (xmlpp::Node *) const; + + boost::shared_ptr<xmlpp::Document> make_kdm ( + CertificateChain const &, + std::string const &, + boost::shared_ptr<const Certificate>, + boost::posix_time::ptime from, + boost::posix_time::ptime until + ) const; private: std::string _directory; diff --git a/src/crypt_chain.cc b/src/crypt_chain.cc new file mode 100644 index 00000000..d0777859 --- /dev/null +++ b/src/crypt_chain.cc @@ -0,0 +1,140 @@ +#include <fstream> +#include <sstream> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string.hpp> +#include "crypt_chain.h" +#include "exceptions.h" + +using std::string; +using std::ofstream; +using std::ifstream; +using std::stringstream; +using std::cout; + +static void command (char const * c) +{ + int const r = system (c); + if (WEXITSTATUS (r)) { + stringstream s; + s << "error in " << c << "\n"; + throw libdcp::MiscError (s.str()); + } +} + +void +libdcp::make_crypt_chain (string directory) +{ + boost::filesystem::current_path (directory); + command ("openssl genrsa -out ca.key 2048"); + + { + ofstream f ("ca.cnf"); + f << "[ req ]\n" + << "distinguished_name = req_distinguished_name\n" + << "x509_extensions = v3_ca\n" + << "[ v3_ca ]\n" + << "basicConstraints = critical,CA:true,pathlen:3\n" + << "keyUsage = keyCertSign,cRLSign\n" + << "subjectKeyIdentifier = hash\n" + << "authorityKeyIdentifier = keyid:always,issuer:always\n" + << "[ req_distinguished_name ]\n" + << "O = Unique organization name\n" + << "OU = Organization unit\n" + << "CN = Entity and dnQualifier\n"; + } + + command ("openssl rsa -outform PEM -pubout -in ca.key | openssl base64 -d | dd bs=1 skip=24 2>/dev/null | openssl sha1 -binary | openssl base64 > ca_dnq"); + + string ca_dnq; + + { + ifstream f ("ca_dnq"); + getline (f, ca_dnq); + boost::replace_all (ca_dnq, "/", "\\/"); + } + + string const ca_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION/dnQualifier=" + ca_dnq; + + { + stringstream c; + c << "openssl req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5 -subj " << ca_subject << " -key ca.key -outform PEM -out ca.self-signed.pem"; + command (c.str().c_str()); + } + + command ("openssl genrsa -out intermediate.key 2048"); + + { + ofstream f ("intermediate.cnf"); + f << "[ default ]\n" + << "distinguished_name = req_distinguished_name\n" + << "x509_extensions = v3_ca\n" + << "[ v3_ca ]\n" + << "basicConstraints = critical,CA:true,pathlen:2\n" + << "keyUsage = keyCertSign,cRLSign\n" + << "subjectKeyIdentifier = hash\n" + << "authorityKeyIdentifier = keyid:always,issuer:always\n" + << "[ req_distinguished_name ]\n" + << "O = Unique organization name\n" + << "OU = Organization unit\n" + << "CN = Entity and dnQualifier\n"; + } + + command ("openssl rsa -outform PEM -pubout -in intermediate.key | openssl base64 -d | dd bs=1 skip=24 2>/dev/null | openssl sha1 -binary | openssl base64 > inter_dnq"); + + string inter_dnq; + + { + ifstream f ("inter_dnq"); + getline (f, inter_dnq); + boost::replace_all (inter_dnq, "/", "\\/"); + } + + string const inter_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION/dnQualifier=" + inter_dnq; + + { + stringstream s; + s << "openssl req -new -config intermediate.cnf -days 3649 -subj " << inter_subject << " -key intermediate.key -out intermediate.csr"; + command (s.str().c_str()); + } + + + command ("openssl x509 -req -sha256 -days 3649 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6 -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem"); + + command ("openssl genrsa -out leaf.key 2048"); + + { + ofstream f ("leaf.cnf"); + f << "[ default ]\n" + << "distinguished_name = req_distinguished_name\n" + << "x509_extensions = v3_ca\n" + << "[ v3_ca ]\n" + << "basicConstraints = critical,CA:false\n" + << "keyUsage = digitalSignature,keyEncipherment\n" + << "subjectKeyIdentifier = hash\n" + << "authorityKeyIdentifier = keyid,issuer:always\n" + << "[ req_distinguished_name ]\n" + << "O = Unique organization name\n" + << "OU = Organization unit\n" + << "CN = Entity and dnQualifier\n"; + } + + command ("openssl rsa -outform PEM -pubout -in leaf.key | openssl base64 -d | dd bs=1 skip=24 2>/dev/null | openssl sha1 -binary | openssl base64 > leaf_dnq"); + + string leaf_dnq; + + { + ifstream f ("leaf_dnq"); + getline (f, leaf_dnq); + boost::replace_all (leaf_dnq, "/", "\\/"); + } + + string const leaf_subject = "/O=example.org/OU=example.org/CN=CS.smpte-430-2.LEAF.NOT_FOR_PRODUCTION/dnQualifier=" + leaf_dnq; + + { + stringstream s; + s << "openssl req -new -config leaf.cnf -days 3648 -subj " << leaf_subject << " -key leaf.key -outform PEM -out leaf.csr"; + command (s.str().c_str()); + } + + command ("openssl x509 -req -sha256 -days 3648 -CA intermediate.signed.pem -CAkey intermediate.key -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem"); +} diff --git a/src/crypt_chain.h b/src/crypt_chain.h new file mode 100644 index 00000000..e8d739d4 --- /dev/null +++ b/src/crypt_chain.h @@ -0,0 +1,6 @@ + +namespace libdcp { + +void make_crypt_chain (std::string); + +} @@ -27,9 +27,12 @@ #include <cassert> #include <iostream> #include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> #include <boost/lexical_cast.hpp> #include <libxml++/libxml++.h> +#include <xmlsec/xmldsig.h> +#include <xmlsec/app.h> #include "dcp.h" #include "asset.h" #include "sound_asset.h" @@ -42,6 +45,7 @@ #include "parse/asset_map.h" #include "reel.h" #include "cpl.h" +#include "encryption.h" using std::string; using std::list; @@ -59,21 +63,21 @@ DCP::DCP (string directory) } void -DCP::write_xml (XMLMetadata const & metadata) const +DCP::write_xml (XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const { for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_xml (metadata); + (*i)->write_xml (metadata, crypt); } string pkl_uuid = make_uuid (); - string pkl_path = write_pkl (pkl_uuid, metadata); + string pkl_path = write_pkl (pkl_uuid, metadata, crypt); write_volindex (); write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path), metadata); } std::string -DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata) const +DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata, shared_ptr<Encryption> crypt) const { assert (!_cpls.empty ()); @@ -84,26 +88,32 @@ DCP::write_pkl (string pkl_uuid, XMLMetadata const & metadata) const p /= s.str(); xmlpp::Document doc; - xmlpp::Element* root = doc.create_root_node ("PackingList", "http://www.smpte-ra.org/schemas/429-8/2007/PKL"); + xmlpp::Element* pkl = doc.create_root_node("PackingList", "http://www.smpte-ra.org/schemas/429-8/2007/PKL"); + if (crypt) { + pkl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); + } - root->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid); + pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid); /* XXX: this is a bit of a hack */ - root->add_child("AnnotationText")->add_child_text (_cpls.front()->name()); - root->add_child("IssueDate")->add_child_text (metadata.issue_date); - root->add_child("Issuer")->add_child_text (metadata.issuer); - root->add_child("Creator")->add_child_text (metadata.creator); - - xmlpp::Node* asset_list = root->add_child ("AssetList"); + pkl->add_child("AnnotationText")->add_child_text(_cpls.front()->name()); + pkl->add_child("IssueDate")->add_child_text (metadata.issue_date); + pkl->add_child("Issuer")->add_child_text (metadata.issuer); + pkl->add_child("Creator")->add_child_text (metadata.creator); + xmlpp::Element* asset_list = pkl->add_child("AssetList"); list<shared_ptr<const Asset> > a = assets (); for (list<shared_ptr<const Asset> >::const_iterator i = a.begin(); i != a.end(); ++i) { (*i)->write_to_pkl (asset_list); } - + for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { (*i)->write_to_pkl (asset_list); } + if (crypt) { + sign (pkl, crypt->certificates, crypt->signer_key); + } + doc.write_to_file_formatted (p.string (), "UTF-8"); return p.string (); } @@ -265,7 +275,6 @@ DCP::equals (DCP const & other, EqualityOptions opt, boost::function<void (NoteT return true; } - void DCP::add_cpl (shared_ptr<CPL> cpl) { @@ -289,8 +298,7 @@ DCP::assets () const a.merge (t); } - a.sort (); + a.sort (AssetComparator ()); a.unique (); return a; } - @@ -29,9 +29,11 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> #include "types.h" +#include "certificates.h" namespace xmlpp { - class Node; + class Document; + class Element; } /** @brief Namespace for everything in libdcp */ @@ -45,6 +47,7 @@ class SubtitleAsset; class Reel; class CPL; class XMLMetadata; +class Encryption; /** @class DCP * @brief A class to create or read a DCP. @@ -74,7 +77,7 @@ public: /** Write the required XML files to the directory that was * passed into the constructor. */ - void write_xml (XMLMetadata const &) const; + void write_xml (XMLMetadata const &, boost::shared_ptr<Encryption> crypt = boost::shared_ptr<Encryption> ()) const; /** Compare this DCP with another, according to various options. * @param other DCP to compare this one to. @@ -103,7 +106,7 @@ private: /** Write the PKL file. * @param pkl_uuid UUID to use. */ - std::string write_pkl (std::string pkl_uuid, XMLMetadata const &) const; + std::string write_pkl (std::string pkl_uuid, XMLMetadata const &, boost::shared_ptr<Encryption>) const; /** Write the VOLINDEX file */ void write_volindex () const; diff --git a/src/dcp_time.cc b/src/dcp_time.cc index bdf55f93..7d3111d2 100644 --- a/src/dcp_time.cc +++ b/src/dcp_time.cc @@ -39,9 +39,14 @@ Time::Time (int frame, int frames_per_second) , s (0) , t (0) { - float sec_float = float (frame) / frames_per_second; - t = (int (floor (sec_float * 1000)) % 1000) / 4; - s = floor (sec_float); + set (double (frame) / frames_per_second); +} + +void +Time::set (double ss) +{ + t = (int (round (ss * 1000)) % 1000) / 4; + s = floor (ss); if (s > 60) { m = s / 60; diff --git a/src/dcp_time.h b/src/dcp_time.h index 48698dcc..1b73e3e8 100644 --- a/src/dcp_time.h +++ b/src/dcp_time.h @@ -62,6 +62,9 @@ public: std::string to_string () const; int64_t to_ticks () const; + +private: + void set (double); }; extern bool operator== (Time const & a, Time const & b); diff --git a/src/mxf_asset.cc b/src/mxf_asset.cc index cfb02c85..d52ab2cc 100644 --- a/src/mxf_asset.cc +++ b/src/mxf_asset.cc @@ -24,11 +24,14 @@ #include <iostream> #include <fstream> #include <boost/filesystem.hpp> +#include <libxml++/nodes/element.h> #include "AS_DCP.h" +#include "KM_prng.h" #include "KM_util.h" #include "mxf_asset.h" #include "util.h" #include "metadata.h" +#include "exceptions.h" using std::string; using std::list; @@ -39,16 +42,45 @@ using namespace libdcp; MXFAsset::MXFAsset (string directory, string file_name) : Asset (directory, file_name) , _progress (0) + , _encrypted (false) + , _encryption_context (0) { } -MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration) +MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted) : Asset (directory, file_name, edit_rate, intrinsic_duration) , _progress (progress) + , _encrypted (encrypted) + , _encryption_context (0) { - + if (_encrypted) { + _key_id = make_uuid (); + uint8_t key_buffer[ASDCP::KeyLen]; + Kumu::FortunaRNG rng; + rng.FillRandom (key_buffer, ASDCP::KeyLen); + char key_string[ASDCP::KeyLen * 4]; + Kumu::bin2hex (key_buffer, ASDCP::KeyLen, key_string, ASDCP::KeyLen * 4); + _key_value = key_string; + + _encryption_context = new ASDCP::AESEncContext; + if (ASDCP_FAILURE (_encryption_context->InitKey (key_buffer))) { + throw MiscError ("could not set up encryption context"); + } + + uint8_t cbc_buffer[ASDCP::CBC_BLOCK_SIZE]; + + if (ASDCP_FAILURE (_encryption_context->SetIVec (rng.FillRandom (cbc_buffer, ASDCP::CBC_BLOCK_SIZE)))) { + throw MiscError ("could not set up CBC initialization vector"); + } + } +} + +MXFAsset::~MXFAsset () +{ + delete _encryption_context; } + void MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info, string uuid, MXFMetadata const & metadata) { @@ -60,6 +92,15 @@ MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info, string uuid, MXFMeta unsigned int c; Kumu::hex2bin (uuid.c_str(), writer_info->AssetUUID, Kumu::UUID_Length, &c); assert (c == Kumu::UUID_Length); + + if (_encrypted) { + Kumu::GenRandomUUID (writer_info->ContextID); + writer_info->EncryptedEssence = true; + + unsigned int c; + Kumu::hex2bin (_key_id.c_str(), writer_info->CryptographicKeyID, Kumu::UUID_Length, &c); + assert (c == Kumu::UUID_Length); + } } bool @@ -84,3 +125,11 @@ MXFAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, boost::fun return true; } + +void +MXFAsset::add_typed_key_id (xmlpp::Element* parent) const +{ + xmlpp::Element* typed_key_id = parent->add_child("TypedKeyId"); + typed_key_id->add_child("KeyType")->add_child_text(key_type ()); + typed_key_id->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); +} diff --git a/src/mxf_asset.h b/src/mxf_asset.h index 55aa889a..a23a052d 100644 --- a/src/mxf_asset.h +++ b/src/mxf_asset.h @@ -23,6 +23,10 @@ #include <boost/signals2.hpp> #include "asset.h" +namespace ASDCP { + class AESEncContext; +} + namespace libdcp { @@ -49,7 +53,9 @@ public: * @param edit_rate Edit rate in frames per second (usually equal to the video frame rate). * @param intrinsic_duration Duration of the whole asset in frames. */ - MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration); + MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int edit_rate, int intrinsic_duration, bool encrypted); + + ~MXFAsset (); virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, boost::function<void (NoteType, std::string)> note) const; @@ -57,12 +63,19 @@ public: * @param w struct to fill in. * @param uuid uuid to use. */ - static void fill_writer_info (ASDCP::WriterInfo* w, std::string uuid, MXFMetadata const & metadata); + void fill_writer_info (ASDCP::WriterInfo* w, std::string uuid, MXFMetadata const & metadata); + void add_typed_key_id (xmlpp::Element *) const; + protected: + virtual std::string key_type () const = 0; /** Signal to emit to report progress, or 0 */ boost::signals2::signal<void (float)>* _progress; + bool _encrypted; + ASDCP::AESEncContext* _encryption_context; + std::string _key_value; + std::string _key_id; }; } diff --git a/src/picture_asset.cc b/src/picture_asset.cc index b5f59f3b..c6a95c74 100644 --- a/src/picture_asset.cc +++ b/src/picture_asset.cc @@ -29,6 +29,7 @@ #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> #include <openjpeg.h> +#include <libxml++/nodes/element.h> #include "AS_DCP.h" #include "KM_fileio.h" #include "picture_asset.h" @@ -41,6 +42,7 @@ using std::ostream; using std::list; using std::vector; using std::max; +using std::stringstream; using std::pair; using std::make_pair; using std::istream; @@ -50,8 +52,8 @@ using boost::dynamic_pointer_cast; using boost::lexical_cast; using namespace libdcp; -PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, Size size) - : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration) +PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size size) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) , _size (size) { @@ -73,6 +75,9 @@ PictureAsset::write_to_cpl (xmlpp::Node* node) const mp->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration)); mp->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point)); mp->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration)); + if (_encrypted) { + mp->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + } mp->add_child ("FrameRate")->add_child_text (lexical_cast<string> (_edit_rate) + " 1"); mp->add_child ("ScreenAspectRatio")->add_child_text (lexical_cast<string> (_size.width) + " " + lexical_cast<string> (_size.height)); } @@ -145,10 +150,11 @@ MonoPictureAsset::MonoPictureAsset ( boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, + bool encrypted, Size size, MXFMetadata const & metadata ) - : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, size) + : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size) { construct (get_path, metadata); } @@ -160,16 +166,17 @@ MonoPictureAsset::MonoPictureAsset ( boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, + bool encrypted, Size size, MXFMetadata const & metadata ) - : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, size) + : PictureAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted, size) { construct (boost::bind (&MonoPictureAsset::path_from_list, this, _1, files), metadata); } MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, Size size) - : PictureAsset (directory, mxf_name, 0, fps, 0, size) + : PictureAsset (directory, mxf_name, 0, fps, 0, false, size) { } @@ -223,7 +230,7 @@ MonoPictureAsset::construct (boost::function<string (int)> get_path, MXFMetadata boost::throw_exception (FileError ("could not open JPEG2000 file for reading", path)); } - if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) { + if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { boost::throw_exception (MXFFileError ("error in writing video MXF", this->path().string())); } @@ -387,7 +394,7 @@ PictureAsset::frame_buffer_equals ( StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int intrinsic_duration) - : PictureAsset (directory, mxf_name, 0, fps, intrinsic_duration, Size (0, 0)) + : PictureAsset (directory, mxf_name, 0, fps, intrinsic_duration, false, Size (0, 0)) { ASDCP::JP2K::MXFSReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { @@ -467,7 +474,7 @@ MonoPictureAssetWriter::start (uint8_t* data, int size) _state->j2k_parser.FillPictureDescriptor (_state->picture_descriptor); _state->picture_descriptor.EditRate = ASDCP::Rational (_asset->edit_rate(), 1); - MXFAsset::fill_writer_info (&_state->writer_info, _asset->uuid(), _metadata); + _asset->fill_writer_info (&_state->writer_info, _asset->uuid(), _metadata); if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite ( _asset->path().string().c_str(), @@ -533,3 +540,9 @@ MonoPictureAssetWriter::finalize () _asset->set_intrinsic_duration (_frames_written); _asset->set_duration (_frames_written); } + +string +PictureAsset::key_type () const +{ + return "MDIK"; +} diff --git a/src/picture_asset.h b/src/picture_asset.h index 8536bacc..08f892ed 100644 --- a/src/picture_asset.h +++ b/src/picture_asset.h @@ -57,7 +57,7 @@ public: * @param intrinsic_duration Duration of all the frames in the asset. * @param size Size of video frame images in pixels. */ - PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, Size size); + PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, bool encrypted, Size); /** Write details of this asset to a CPL XML node. * @param node Node. @@ -72,6 +72,8 @@ public: protected: + std::string key_type () const; + bool frame_buffer_equals ( int frame, EqualityOptions opt, boost::function<void (NoteType, std::string)> note, uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B @@ -158,6 +160,7 @@ public: * @param fps Video frames per second. * @param intrinsic_duration Length of the whole asset in frames. * @param size Size of images in pixels. + * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( std::vector<std::string> const & files, @@ -166,6 +169,7 @@ public: boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, + bool encrypted, Size size, MXFMetadata const & metadata = MXFMetadata () ); @@ -180,6 +184,7 @@ public: * @param fps Video frames per second. * @param intrinsic_duration Length of the whole asset in frames. * @param size Size of images in pixels. + * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( boost::function<std::string (int)> get_path, @@ -188,6 +193,7 @@ public: boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, + bool encrypted, Size size, MXFMetadata const & metadata = MXFMetadata () ); diff --git a/src/reel.cc b/src/reel.cc index 481b153b..3f077269 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -17,6 +17,7 @@ */ +#include <libxml++/nodes/element.h> #include "reel.h" #include "util.h" #include "picture_asset.h" @@ -23,6 +23,10 @@ #include <libxml++/libxml++.h> #include "types.h" +namespace xmlpp { + class Node; +} + namespace libdcp { class PictureAsset; diff --git a/src/sound_asset.cc b/src/sound_asset.cc index f59d82ad..45a65646 100644 --- a/src/sound_asset.cc +++ b/src/sound_asset.cc @@ -25,6 +25,7 @@ #include <stdexcept> #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> +#include <libxml++/nodes/element.h> #include "KM_fileio.h" #include "AS_DCP.h" #include "sound_asset.h" @@ -46,10 +47,12 @@ SoundAsset::SoundAsset ( string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, - int fps, int intrinsic_duration, + int fps, + int intrinsic_duration, + bool encrypted, MXFMetadata const & metadata ) - : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) , _channels (files.size ()) , _sampling_rate (0) { @@ -63,10 +66,13 @@ SoundAsset::SoundAsset ( string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, - int fps, int intrinsic_duration, int channels, + int fps, + int intrinsic_duration, + int channels, + bool encrypted, MXFMetadata const & metadata ) - : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration) + : MXFAsset (directory, mxf_name, progress, fps, intrinsic_duration, encrypted) , _channels (channels) , _sampling_rate (0) { @@ -97,7 +103,7 @@ SoundAsset::SoundAsset (string directory, string mxf_name) } SoundAsset::SoundAsset (string directory, string mxf_name, int fps, int channels, int sampling_rate) - : MXFAsset (directory, mxf_name, 0, fps, 0) + : MXFAsset (directory, mxf_name, 0, fps, 0, false) , _channels (channels) , _sampling_rate (sampling_rate) { @@ -196,7 +202,7 @@ SoundAsset::construct (boost::function<string (Channel)> get_path, MXFMetadata c offset += sample_size; } - if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) { + if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { boost::throw_exception (MiscError ("could not write audio MXF frame")); } @@ -220,6 +226,9 @@ SoundAsset::write_to_cpl (xmlpp::Node* node) const ms->add_child ("IntrinsicDuration")->add_child_text (lexical_cast<string> (_intrinsic_duration)); ms->add_child ("EntryPoint")->add_child_text (lexical_cast<string> (_entry_point)); ms->add_child ("Duration")->add_child_text (lexical_cast<string> (_duration)); + if (_encrypted) { + ms->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + } } bool @@ -341,7 +350,7 @@ SoundAssetWriter::SoundAssetWriter (SoundAsset* a, MXFMetadata const & m) _state->frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (_state->audio_desc)); memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity()); - MXFAsset::fill_writer_info (&_state->writer_info, _asset->uuid (), _metadata); + _asset->fill_writer_info (&_state->writer_info, _asset->uuid (), _metadata); if (ASDCP_FAILURE (_state->mxf_writer.OpenWrite (_asset->path().string().c_str(), _state->writer_info, _state->audio_desc))) { boost::throw_exception (FileError ("could not open audio MXF for writing", _asset->path().string())); @@ -400,3 +409,9 @@ SoundAssetWriter::finalize () _asset->set_intrinsic_duration (_frames_written); _asset->set_duration (_frames_written); } + +string +SoundAsset::key_type () const +{ + return "MDAK"; +} diff --git a/src/sound_asset.h b/src/sound_asset.h index 7b9c65a7..a587b551 100644 --- a/src/sound_asset.h +++ b/src/sound_asset.h @@ -76,7 +76,10 @@ public: * @param mxf_name Name of MXF file to create. * @param progress Signal to inform of progress. * @param fps Frames per second. + * @param length Length in frames. + * @param start_frame Frame in the source to start writing from. * @param intrinsic_duration Length of the whole asset in frames. + * @param encrypted true if asset should be encrypted. * Note that this is different to entry_point in that the asset will contain no data before start_frame. */ SoundAsset ( @@ -86,6 +89,7 @@ public: boost::signals2::signal<void (float)>* progress, int fps, int intrinsic_duration, + bool encrypted, MXFMetadata const & metadata = MXFMetadata () ); @@ -98,6 +102,7 @@ public: * @param fps Frames per second. * @param intrinsic_duration Length of the whole asset in frames. * @param channels Number of audio channels. + * @param encrypted true if asset should be encrypted. */ SoundAsset ( boost::function<std::string (Channel)> get_path, @@ -107,6 +112,7 @@ public: int fps, int intrinsic_duration, int channels, + bool encrypted, MXFMetadata const & metadata = MXFMetadata () ); @@ -143,6 +149,7 @@ public: } private: + std::string key_type () const; void construct (boost::function<std::string (Channel)> get_path, MXFMetadata const &); std::string path_from_channel (Channel channel, std::vector<std::string> const & files); diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 2a244a27..7f4d47d0 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -20,6 +20,7 @@ #include <fstream> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> +#include <libxml++/nodes/element.h> #include "subtitle_asset.h" #include "parse/subtitle.h" #include "util.h" diff --git a/src/util.cc b/src/util.cc index 6bee0dc7..0c63c305 100644 --- a/src/util.cc +++ b/src/util.cc @@ -28,6 +28,11 @@ #include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> #include <openssl/sha.h> +#include <libxml++/nodes/element.h> +#include <libxml++/document.h> +#include <xmlsec/xmldsig.h> +#include <xmlsec/dl.h> +#include <xmlsec/app.h> #include "KM_util.h" #include "KM_fileio.h" #include "AS_DCP.h" @@ -35,12 +40,15 @@ #include "exceptions.h" #include "types.h" #include "argb_frame.h" +#include "certificates.h" #include "gamma_lut.h" using std::string; +using std::cout; using std::stringstream; using std::min; using std::max; +using std::list; using boost::shared_ptr; using boost::lexical_cast; using namespace libdcp; @@ -93,7 +101,6 @@ libdcp::make_digest (string filename) byte_t byte_buffer[20]; SHA1_Final (byte_buffer, &sha); - stringstream s; char digest[64]; return Kumu::base64encode (byte_buffer, 20, digest, 64); } @@ -294,6 +301,133 @@ libdcp::empty_or_white_space (string s) return true; } +void +libdcp::init () +{ + if (xmlSecInit() < 0) { + throw MiscError ("could not initialise xmlsec"); + } + +#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING + if (xmlSecCryptoDLLoadLibrary (BAD_CAST XMLSEC_CRYPTO) < 0) { + throw MiscError ("unable to load default xmlsec-crypto library"); + } +#endif + + if (xmlSecCryptoAppInit (0) < 0) { + throw MiscError ("could not initialise crypto library"); + } + + if (xmlSecCryptoInit() < 0) { + throw MiscError ("could not initialise xmlsec-crypto"); + } +} + +void +libdcp::add_signature_value (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key, string const & ns) +{ + parent->add_child("SignatureValue", ns); + + xmlpp::Element* key_info = parent->add_child("KeyInfo", ns); + list<shared_ptr<Certificate> > c = certificates.leaf_to_root (); + for (list<shared_ptr<Certificate> >::iterator i = c.begin(); i != c.end(); ++i) { + xmlpp::Element* data = key_info->add_child("X509Data", ns); + + { + xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns); + serial->add_child("X509IssuerName", ns)->add_child_text( + Certificate::name_for_xml ((*i)->issuer()) + ); + serial->add_child("X509SerialNumber", ns)->add_child_text((*i)->serial()); + } + + data->add_child("X509Certificate", ns)->add_child_text((*i)->certificate()); + } + + xmlSecKeysMngrPtr keys_manager = xmlSecKeysMngrCreate(); + if (!keys_manager) { + throw MiscError ("could not create keys manager"); + } + if (xmlSecCryptoAppDefaultKeysMngrInit (keys_manager) < 0) { + throw MiscError ("could not initialise keys manager"); + } + + xmlSecKeyPtr const key = xmlSecCryptoAppKeyLoad (signer_key.c_str(), xmlSecKeyDataFormatPem, 0, 0, 0); + if (key == 0) { + throw MiscError ("could not load signer key"); + } + + if (xmlSecCryptoAppDefaultKeysMngrAdoptKey (keys_manager, key) < 0) { + xmlSecKeyDestroy (key); + throw MiscError ("could not use signer key"); + } + + xmlSecDSigCtx signature_context; + + if (xmlSecDSigCtxInitialize (&signature_context, keys_manager) < 0) { + throw MiscError ("could not initialise XMLSEC context"); + } + + if (xmlSecDSigCtxSign (&signature_context, parent->cobj()) < 0) { + throw MiscError ("could not sign"); + } + + xmlSecDSigCtxFinalize (&signature_context); + xmlSecKeysMngrDestroy (keys_manager); +} + + +void +libdcp::add_signer (xmlpp::Element* parent, CertificateChain const & certificates, string const & ns) +{ + xmlpp::Element* signer = parent->add_child("Signer"); + + { + xmlpp::Element* data = signer->add_child("X509Data", ns); + + { + xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", ns); + serial_element->add_child("X509IssuerName", ns)->add_child_text ( + Certificate::name_for_xml (certificates.leaf()->issuer()) + ); + serial_element->add_child("X509SerialNumber", ns)->add_child_text ( + certificates.leaf()->serial() + ); + } + + data->add_child("X509SubjectName", ns)->add_child_text (Certificate::name_for_xml (certificates.leaf()->subject())); + } +} + +void +libdcp::sign (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key) +{ + add_signer (parent, certificates, "dsig"); + + xmlpp::Element* signature = parent->add_child("Signature", "dsig"); + + { + xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig"); + signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); + signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); + { + xmlpp::Element* reference = signed_info->add_child("Reference", "dsig"); + reference->set_attribute ("URI", ""); + { + xmlpp::Element* transforms = reference->add_child("Transforms", "dsig"); + transforms->add_child("Transform", "dsig")->set_attribute ( + "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature" + ); + } + reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); + /* This will be filled in by the signing later */ + reference->add_child("DigestValue", "dsig"); + } + } + + add_signature_value (signature, certificates, signer_key, "dsig"); +} + bool libdcp::operator== (libdcp::Size const & a, libdcp::Size const & b) { return (a.width == b.width && a.height == b.height); @@ -30,9 +30,14 @@ #include <openjpeg.h> #include "types.h" +namespace xmlpp { + class Element; +} + namespace libdcp { class ARGBFrame; +class CertificateChain; class GammaLUT; class XYZsRGBLUT; @@ -62,6 +67,12 @@ extern bool empty_or_white_space (std::string s); extern opj_image_t* decompress_j2k (uint8_t* data, int64_t size, int reduce); extern boost::shared_ptr<ARGBFrame> xyz_to_rgb (opj_image_t* xyz_frame, boost::shared_ptr<const GammaLUT>, boost::shared_ptr<const GammaLUT>); +extern void init (); + +extern void sign (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & signer_key); +extern void add_signature_value (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & signer_key, std::string const & ns); +extern void add_signer (xmlpp::Element* parent, CertificateChain const & certificates, std::string const & ns); + } #endif diff --git a/src/wscript b/src/wscript index 37151e51..93d6d5c1 100644 --- a/src/wscript +++ b/src/wscript @@ -7,11 +7,13 @@ def build(bld): obj.name = 'libdcp' obj.target = 'dcp' obj.export_includes = ['.'] - obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 OPENSSL SIGC++ LIBXML++ OPENJPEG CXML' + obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1' obj.use = 'libkumu-libdcp libasdcp-libdcp' obj.source = """ asset.cc dcp.cc + certificates.cc + crypt_chain.cc cpl.cc dcp_time.cc gamma_lut.cc @@ -35,7 +37,9 @@ def build(bld): headers = """ asset.h + certificates.h cpl.h + crypt_chain.h dcp.h dcp_time.h exceptions.h |
