diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/asset.cc | 18 | ||||
| -rw-r--r-- | src/asset.h | 16 | ||||
| -rw-r--r-- | src/certificates.cc | 189 | ||||
| -rw-r--r-- | src/certificates.h | 80 | ||||
| -rw-r--r-- | src/crypt_chain.cc | 140 | ||||
| -rw-r--r-- | src/crypt_chain.h | 6 | ||||
| -rw-r--r-- | src/dcp.cc | 272 | ||||
| -rw-r--r-- | src/dcp.h | 33 | ||||
| -rw-r--r-- | src/dcp_time.cc | 11 | ||||
| -rw-r--r-- | src/dcp_time.h | 3 | ||||
| -rw-r--r-- | src/mxf_asset.cc | 50 | ||||
| -rw-r--r-- | src/mxf_asset.h | 18 | ||||
| -rw-r--r-- | src/picture_asset.cc | 57 | ||||
| -rw-r--r-- | src/picture_asset.h | 21 | ||||
| -rw-r--r-- | src/reel.cc | 17 | ||||
| -rw-r--r-- | src/reel.h | 6 | ||||
| -rw-r--r-- | src/sound_asset.cc | 50 | ||||
| -rw-r--r-- | src/sound_asset.h | 14 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 14 | ||||
| -rw-r--r-- | src/subtitle_asset.h | 6 | ||||
| -rw-r--r-- | src/util.cc | 137 | ||||
| -rw-r--r-- | src/util.h | 13 | ||||
| -rw-r--r-- | src/wscript | 7 |
23 files changed, 1034 insertions, 144 deletions
diff --git a/src/asset.cc b/src/asset.cc index 3d2a0e03..fa6947d1 100644 --- a/src/asset.cc +++ b/src/asset.cc @@ -24,6 +24,8 @@ #include <iostream> #include <fstream> #include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <libxml++/nodes/element.h> #include "AS_DCP.h" #include "KM_util.h" #include "asset.h" @@ -45,15 +47,14 @@ Asset::Asset (string directory, string file_name) } void -Asset::write_to_pkl (ostream& s) const +Asset::write_to_pkl (xmlpp::Element* p) const { - s << " <Asset>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <AnnotationText>" << _file_name << "</AnnotationText>\n" - << " <Hash>" << digest() << "</Hash>\n" - << " <Size>" << filesystem::file_size(path()) << "</Size>\n" - << " <Type>application/mxf</Type>\n" - << " </Asset>\n"; + xmlpp::Element* asset = p->add_child("Asset"); + asset->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + asset->add_child("AnnotationText")->add_child_text (_file_name); + asset->add_child("Hash")->add_child_text (digest()); + asset->add_child("Size")->add_child_text (boost::lexical_cast<string> (filesystem::file_size(path()))); + asset->add_child("Type")->add_child_text ("application/mxf"); } void @@ -90,3 +91,4 @@ Asset::digest () const return _digest; } + diff --git a/src/asset.h b/src/asset.h index cc6fbcb7..5436e05c 100644 --- a/src/asset.h +++ b/src/asset.h @@ -33,6 +33,10 @@ namespace ASDCP { class WriterInfo; } +namespace xmlpp { + class Element; +} + namespace libdcp { @@ -51,15 +55,15 @@ 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 (std::ostream& s) const = 0; + virtual void write_to_cpl (xmlpp::Element* p) 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 (std::ostream& s) const; + void write_to_pkl (xmlpp::Element* p) const; /** Write details of the asset to a ASSETMAP stream. * @param s Stream. diff --git a/src/certificates.cc b/src/certificates.cc new file mode 100644 index 00000000..c1b15c51 --- /dev/null +++ b/src/certificates.cc @@ -0,0 +1,189 @@ +/* + 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 +{ + 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 +{ + 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 +{ + 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 +{ + 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 +{ + 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 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,8 +27,11 @@ #include <cassert> #include <iostream> #include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> #include <libxml++/libxml++.h> +#include <xmlsec/xmldsig.h> +#include <xmlsec/app.h> #include "dcp.h" #include "asset.h" #include "sound_asset.h" @@ -57,21 +60,21 @@ DCP::DCP (string directory) } void -DCP::write_xml () const +DCP::write_xml (shared_ptr<Encryption> crypt) const { for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_xml (); + (*i)->write_xml (crypt); } string pkl_uuid = make_uuid (); - string pkl_path = write_pkl (pkl_uuid); + string pkl_path = write_pkl (pkl_uuid, crypt); write_volindex (); write_assetmap (pkl_uuid, boost::filesystem::file_size (pkl_path)); } std::string -DCP::write_pkl (string pkl_uuid) const +DCP::write_pkl (string pkl_uuid, shared_ptr<Encryption> crypt) const { assert (!_cpls.empty ()); @@ -80,29 +83,37 @@ DCP::write_pkl (string pkl_uuid) const stringstream s; s << pkl_uuid << "_pkl.xml"; p /= s.str(); - ofstream pkl (p.string().c_str()); - - pkl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<PackingList xmlns=\"http://www.smpte-ra.org/schemas/429-8/2007/PKL\">\n" - << " <Id>urn:uuid:" << pkl_uuid << "</Id>\n" - /* XXX: this is a bit of a hack */ - << " <AnnotationText>" << _cpls.front()->name() << "</AnnotationText>\n" - << " <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n" - << " <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n" - << " <Creator>" << Metadata::instance()->creator << "</Creator>\n" - << " <AssetList>\n"; - 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 (pkl); + xmlpp::Document doc; + 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"); } - for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) { - (*i)->write_to_pkl (pkl); + pkl->add_child("Id")->add_child_text ("urn:uuid:" + pkl_uuid); + /* XXX: this is a bit of a hack */ + pkl->add_child("AnnotationText")->add_child_text(_cpls.front()->name()); + pkl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date); + pkl->add_child("Issuer")->add_child_text (Metadata::instance()->issuer); + pkl->add_child("Creator")->add_child_text (Metadata::instance()->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); + } } - pkl << " </AssetList>\n" - << "</PackingList>\n"; + if (crypt) { + sign (pkl, crypt->certificates, crypt->signer_key); + } + + doc.write_to_file_formatted (p.string(), "UTF-8"); return p.string (); } @@ -293,7 +304,7 @@ DCP::assets () const a.merge (t); } - a.sort (); + a.sort (AssetComparator ()); a.unique (); return a; } @@ -430,52 +441,59 @@ CPL::add_reel (shared_ptr<const Reel> reel) } void -CPL::write_xml () const +CPL::write_xml (shared_ptr<Encryption> crypt) const { boost::filesystem::path p; p /= _directory; stringstream s; s << _uuid << "_cpl.xml"; p /= s.str(); - ofstream os (p.string().c_str()); - - os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<CompositionPlaylist xmlns=\"http://www.smpte-ra.org/schemas/429-7/2006/CPL\">\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <AnnotationText>" << _name << "</AnnotationText>\n" - << " <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n" - << " <Creator>" << Metadata::instance()->creator << "</Creator>\n" - << " <ContentTitleText>" << _name << "</ContentTitleText>\n" - << " <ContentKind>" << content_kind_to_string (_content_kind) << "</ContentKind>\n" - << " <ContentVersion>\n" - << " <Id>urn:uri:" << _uuid << "_" << Metadata::instance()->issue_date << "</Id>\n" - << " <LabelText>" << _uuid << "_" << Metadata::instance()->issue_date << "</LabelText>\n" - << " </ContentVersion>\n" - << " <RatingList/>\n" - << " <ReelList>\n"; - + + xmlpp::Document doc; + xmlpp::Element* cpl = doc.create_root_node("CompositionPlaylist", "http://www.smpte-ra.org/schemas/429-7/2006/CPL"); + + if (crypt) { + cpl->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); + } + + cpl->add_child("Id")->add_child_text ("urn:uuid:" + _uuid); + cpl->add_child("AnnotationText")->add_child_text (_name); + cpl->add_child("IssueDate")->add_child_text (Metadata::instance()->issue_date); + cpl->add_child("Creator")->add_child_text (Metadata::instance()->creator); + cpl->add_child("ContentTitleText")->add_child_text (_name); + cpl->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind)); + + { + xmlpp::Element* cv = cpl->add_child ("ContentVersion"); + cv->add_child("Id")->add_child_text ("urn:uri:" + _uuid + "_" + Metadata::instance()->issue_date); + cv->add_child("LabelText")->add_child_text (_uuid + "_" + Metadata::instance()->issue_date); + } + + cpl->add_child("RatingList"); + + xmlpp::Element* reel_list = cpl->add_child("ReelList"); for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) { - (*i)->write_to_cpl (os); + (*i)->write_to_cpl (reel_list); } - os << " </ReelList>\n" - << "</CompositionPlaylist>\n"; + if (crypt) { + sign (cpl, crypt->certificates, crypt->signer_key); + } - os.close (); + doc.write_to_file_formatted (p.string(), "UTF-8"); _digest = make_digest (p.string ()); _length = boost::filesystem::file_size (p.string ()); } void -CPL::write_to_pkl (ostream& s) const +CPL::write_to_pkl (xmlpp::Element* p) const { - s << " <Asset>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <Hash>" << _digest << "</Hash>\n" - << " <Size>" << _length << "</Size>\n" - << " <Type>text/xml</Type>\n" - << " </Asset>\n"; + xmlpp::Element* asset = p->add_child("Asset"); + asset->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + asset->add_child("Hash")->add_child_text(_digest); + asset->add_child("Size")->add_child_text(boost::lexical_cast<string> (_length)); + asset->add_child("Type")->add_child_text("text/xml"); } list<shared_ptr<const Asset> > @@ -556,3 +574,153 @@ CPL::equals (CPL const & other, EqualityOptions opt, list<string>& notes) const 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 +{ + 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(Metadata::instance()->product_name); + authenticated_public->add_child("IssueDate")->add_child_text(Metadata::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; +} @@ -28,10 +28,13 @@ #include <vector> #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> #include "types.h" +#include "certificates.h" namespace xmlpp { - class Node; + class Document; + class Element; } /** @brief Namespace for everything in libdcp */ @@ -45,6 +48,18 @@ class SubtitleAsset; class Reel; class AssetMap; +class Encryption +{ +public: + Encryption (CertificateChain c, std::string const & k) + : certificates (c) + , signer_key (k) + {} + + CertificateChain certificates; + std::string signer_key; +}; + /** @brief A CPL within a DCP */ class CPL { @@ -86,9 +101,17 @@ public: bool equals (CPL const & other, EqualityOptions options, std::list<std::string>& notes) const; - void write_xml () const; + void write_xml (boost::shared_ptr<Encryption>) const; void write_to_assetmap (std::ostream& s) const; - void write_to_pkl (std::ostream& s) const; + void write_to_pkl (xmlpp::Element* p) 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; @@ -137,7 +160,7 @@ public: /** Write the required XML files to the directory that was * passed into the constructor. */ - void write_xml () const; + void write_xml (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. @@ -167,7 +190,7 @@ private: /** Write the PKL file. * @param pkl_uuid UUID to use. */ - std::string write_pkl (std::string pkl_uuid) const; + std::string write_pkl (std::string pkl_uuid, 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 15ad05d4..7c7c5298 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 95412d0c..6ba42d75 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; @@ -36,14 +39,40 @@ using boost::shared_ptr; using boost::dynamic_pointer_cast; using namespace libdcp; -MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length) +MXFAsset::MXFAsset (string directory, string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted) : Asset (directory, file_name) , _progress (progress) , _fps (fps) , _entry_point (entry_point) , _length (length) + , _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 @@ -57,6 +86,15 @@ MXFAsset::fill_writer_info (ASDCP::WriterInfo* writer_info) const 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 @@ -91,3 +129,11 @@ MXFAsset::length () const { return _length; } + +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 b1cb87fe..93fa9013 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 { @@ -37,14 +41,22 @@ public: * @param fps Frames per second. * @param entry_point The entry point of this MXF; ie the first frame that should be used. * @param length Length in frames. + * @param encrypted true if the MXF should be encrypted. */ - MXFAsset (std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length); + MXFAsset ( + std::string directory, std::string file_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted + ); + + ~MXFAsset (); virtual bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; int length () const; + void add_typed_key_id (xmlpp::Element *) const; protected: + virtual std::string key_type () const = 0; + /** Fill in a ADSCP::WriteInfo struct. * @param w struct to fill in. */ @@ -57,6 +69,10 @@ protected: int _entry_point; /** Length in frames */ int _length; + 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 ef5d40d4..f783bb39 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,13 +42,14 @@ using std::ostream; using std::list; using std::vector; using std::max; +using std::stringstream; using boost::shared_ptr; 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 entry_point, int length) - : MXFAsset (directory, mxf_name, progress, fps, entry_point, length) +PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted) + : MXFAsset (directory, mxf_name, progress, fps, entry_point, length, encrypted) , _width (0) , _height (0) { @@ -55,18 +57,22 @@ PictureAsset::PictureAsset (string directory, string mxf_name, boost::signals2:: } void -PictureAsset::write_to_cpl (ostream& s) const +PictureAsset::write_to_cpl (xmlpp::Element* parent) const { - s << " <MainPicture>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <AnnotationText>" << _file_name << "</AnnotationText>\n" - << " <EditRate>" << _fps << " 1</EditRate>\n" - << " <IntrinsicDuration>" << _length << "</IntrinsicDuration>\n" - << " <EntryPoint>0</EntryPoint>\n" - << " <Duration>" << _length << "</Duration>\n" - << " <FrameRate>" << _fps << " 1</FrameRate>\n" - << " <ScreenAspectRatio>" << _width << " " << _height << "</ScreenAspectRatio>\n" - << " </MainPicture>\n"; + xmlpp::Element* main_picture = parent->add_child("MainPicture"); + main_picture->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + main_picture->add_child("AnnotationText")->add_child_text(_file_name); + main_picture->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); + main_picture->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length)); + main_picture->add_child("EntryPoint")->add_child_text("0"); + main_picture->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length)); + if (_encrypted) { + main_picture->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + } + main_picture->add_child("FrameRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); + stringstream sar; + sar << _width << " " << _height; + main_picture->add_child("ScreenAspectRatio")->add_child_text(sar.str()); } bool @@ -138,8 +144,9 @@ MonoPictureAsset::MonoPictureAsset ( int fps, int length, int width, - int height) - : PictureAsset (directory, mxf_name, progress, fps, 0, length) + int height, + bool encrypted) + : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted) { _width = width; _height = height; @@ -154,8 +161,9 @@ MonoPictureAsset::MonoPictureAsset ( int fps, int length, int width, - int height) - : PictureAsset (directory, mxf_name, progress, fps, 0, length) + int height, + bool encrypted) + : PictureAsset (directory, mxf_name, progress, fps, 0, length, encrypted) { _width = width; _height = height; @@ -163,7 +171,7 @@ MonoPictureAsset::MonoPictureAsset ( } MonoPictureAsset::MonoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : PictureAsset (directory, mxf_name, 0, fps, entry_point, length) + : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false) { ASDCP::JP2K::MXFReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { @@ -194,7 +202,7 @@ MonoPictureAsset::construct (boost::function<string (int)> get_path) ASDCP::WriterInfo writer_info; fill_writer_info (&writer_info); - + ASDCP::JP2K::MXFWriter mxf_writer; if (ASDCP_FAILURE (mxf_writer.OpenWrite (path().string().c_str(), writer_info, picture_desc))) { throw MXFFileError ("could not open MXF file for writing", path().string()); @@ -208,8 +216,7 @@ MonoPictureAsset::construct (boost::function<string (int)> get_path) throw FileError ("could not open JPEG2000 file for reading", path); } - /* XXX: passing 0 to WriteFrame ok? */ - if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) { + if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { throw MiscError ("error in writing video MXF"); } @@ -363,7 +370,7 @@ PictureAsset::frame_buffer_equals ( StereoPictureAsset::StereoPictureAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : PictureAsset (directory, mxf_name, 0, fps, entry_point, length) + : PictureAsset (directory, mxf_name, 0, fps, entry_point, length, false) { ASDCP::JP2K::MXFSReader reader; if (ASDCP_FAILURE (reader.OpenRead (path().string().c_str()))) { @@ -384,3 +391,9 @@ StereoPictureAsset::get_frame (int n) const { return shared_ptr<const StereoPictureFrame> (new StereoPictureFrame (path().string(), n + _entry_point)); } + +string +PictureAsset::key_type () const +{ + return "MDIK"; +} diff --git a/src/picture_asset.h b/src/picture_asset.h index 08eb338a..96bf5659 100644 --- a/src/picture_asset.h +++ b/src/picture_asset.h @@ -34,12 +34,14 @@ class StereoPictureFrame; class PictureAsset : public MXFAsset { public: - PictureAsset (std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length); + PictureAsset ( + std::string directory, std::string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int entry_point, int length, bool encrypted + ); - /** Write details of this asset to a CPL stream. - * @param s Stream. + /** Write details of the asset to a CPL AssetList node. + * @param p Parent node. */ - void write_to_cpl (std::ostream& s) const; + void write_to_cpl (xmlpp::Element* p) const; bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; @@ -62,6 +64,9 @@ protected: int _width; /** picture height in pixels */ int _height; + +private: + std::string key_type () const; }; /** A 2D (monoscopic) picture asset */ @@ -78,6 +83,7 @@ public: * @param length Length in frames. * @param width Width of images in pixels. * @param height Height of images in pixels. + * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( std::vector<std::string> const & files, @@ -87,7 +93,8 @@ public: int fps, int length, int width, - int height + int height, + bool encrypted ); /** Construct a PictureAsset, generating the MXF from the JPEG2000 files. @@ -100,6 +107,7 @@ public: * @param length Length in frames. * @param width Width of images in pixels. * @param height Height of images in pixels. + * @param encrypted true if asset should be encrypted. */ MonoPictureAsset ( boost::function<std::string (int)> get_path, @@ -109,7 +117,8 @@ public: int fps, int length, int width, - int height + int height, + bool encrypted ); MonoPictureAsset (std::string directory, std::string mxf_name, int fps, int entry_point, int length); diff --git a/src/reel.cc b/src/reel.cc index 8995f874..ae3080ad 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" @@ -27,22 +28,22 @@ using namespace std; using namespace libdcp; void -Reel::write_to_cpl (ostream& s) const +Reel::write_to_cpl (xmlpp::Node* parent) const { - s << " <Reel>\n" - << " <Id>urn:uuid:" << make_uuid() << "</Id>\n" - << " <AssetList>\n"; - + xmlpp::Element* reel = parent->add_child("Reel"); + reel->add_child("Id")->add_child_text("urn:uuid:" + make_uuid()); + xmlpp::Element* asset_list = reel->add_child("AssetList"); + if (_main_picture) { - _main_picture->write_to_cpl (s); + _main_picture->write_to_cpl (asset_list); } if (_main_sound) { - _main_sound->write_to_cpl (s); + _main_sound->write_to_cpl (asset_list); } if (_main_subtitle) { - _main_subtitle->write_to_cpl (s); + _main_subtitle->write_to_cpl (asset_list); } s << " </AssetList>\n" @@ -21,6 +21,10 @@ #include <boost/shared_ptr.hpp> #include "types.h" +namespace xmlpp { + class Node; +} + namespace libdcp { class PictureAsset; @@ -53,7 +57,7 @@ public: return _main_subtitle; } - void write_to_cpl (std::ostream & s) const; + void write_to_cpl (xmlpp::Node *) const; bool equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, std::list<std::string>& notes) const; diff --git a/src/sound_asset.cc b/src/sound_asset.cc index 98266b28..d964a46d 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" @@ -42,9 +43,16 @@ using boost::lexical_cast; using namespace libdcp; SoundAsset::SoundAsset ( - vector<string> const & files, string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, int fps, int length, int start_frame + vector<string> const & files, + string directory, + string mxf_name, + boost::signals2::signal<void (float)>* progress, + int fps, + int length, + int start_frame, + bool encrypted ) - : MXFAsset (directory, mxf_name, progress, fps, 0, length) + : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted) , _channels (files.size ()) , _sampling_rate (0) , _start_frame (start_frame) @@ -59,9 +67,13 @@ SoundAsset::SoundAsset ( string directory, string mxf_name, boost::signals2::signal<void (float)>* progress, - int fps, int length, int start_frame, int channels + int fps, + int length, + int start_frame, + int channels, + bool encrypted ) - : MXFAsset (directory, mxf_name, progress, fps, 0, length) + : MXFAsset (directory, mxf_name, progress, fps, 0, length, encrypted) , _channels (channels) , _sampling_rate (0) , _start_frame (start_frame) @@ -72,7 +84,7 @@ SoundAsset::SoundAsset ( } SoundAsset::SoundAsset (string directory, string mxf_name, int fps, int entry_point, int length) - : MXFAsset (directory, mxf_name, 0, fps, entry_point, length) + : MXFAsset (directory, mxf_name, 0, fps, entry_point, length, false) , _channels (0) , _start_frame (0) { @@ -192,7 +204,7 @@ SoundAsset::construct (boost::function<string (Channel)> get_path) offset += sample_size; } - if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, 0, 0))) { + if (ASDCP_FAILURE (mxf_writer.WriteFrame (frame_buffer, _encryption_context, 0))) { throw MiscError ("could not write audio MXF frame"); } @@ -207,16 +219,18 @@ SoundAsset::construct (boost::function<string (Channel)> get_path) } void -SoundAsset::write_to_cpl (ostream& s) const +SoundAsset::write_to_cpl (xmlpp::Element* parent) const { - s << " <MainSound>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <AnnotationText>" << _file_name << "</AnnotationText>\n" - << " <EditRate>" << _fps << " 1</EditRate>\n" - << " <IntrinsicDuration>" << _length << "</IntrinsicDuration>\n" - << " <EntryPoint>0</EntryPoint>\n" - << " <Duration>" << _length << "</Duration>\n" - << " </MainSound>\n"; + xmlpp::Element* main_sound = parent->add_child("MainSound"); + main_sound->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + main_sound->add_child("AnnotationText")->add_child_text(_file_name); + main_sound->add_child("EditRate")->add_child_text(boost::lexical_cast<string> (_fps) + " 1"); + main_sound->add_child("IntrinsicDuration")->add_child_text(boost::lexical_cast<string> (_length)); + main_sound->add_child("EntryPoint")->add_child_text("0"); + main_sound->add_child("Duration")->add_child_text(boost::lexical_cast<string> (_length)); + if (_encrypted) { + main_sound->add_child("KeyId")->add_child_text("urn:uuid:" + _key_id); + } } bool @@ -298,3 +312,9 @@ SoundAsset::get_frame (int n) const { return shared_ptr<const SoundFrame> (new SoundFrame (path().string(), n + _entry_point)); } + +string +SoundAsset::key_type () const +{ + return "MDAK"; +} diff --git a/src/sound_asset.h b/src/sound_asset.h index 9fb1d60b..2b925641 100644 --- a/src/sound_asset.h +++ b/src/sound_asset.h @@ -45,6 +45,7 @@ public: * @param fps Frames per second. * @param length Length in frames. * @param start_frame Frame in the source to start writing from. + * @param encrypted true if asset should be encrypted. */ SoundAsset ( std::vector<std::string> const & files, @@ -54,6 +55,7 @@ public: int fps, int length, int start_frame + bool encrypted ); /** Construct a SoundAsset, generating the MXF from some WAV files. @@ -66,6 +68,7 @@ public: * @param length Length in frames. * @param start_frame Frame in the source to start writing from. * @param channels Number of audio channels. + * @param encrypted true if asset should be encrypted. */ SoundAsset ( boost::function<std::string (Channel)> get_path, @@ -75,7 +78,8 @@ public: int fps, int length, int start_frame, - int channels + int channels, + bool encrypted ); SoundAsset ( @@ -86,10 +90,10 @@ public: int length ); - /** Write details of this asset to a CPL stream. - * @param s Stream. + /** Write details of the asset to a CPL AssetList node. + * @param p Parent node. */ - void write_to_cpl (std::ostream& s) const; + void write_to_cpl (xmlpp::Element* p) const; bool equals (boost::shared_ptr<const Asset> other, EqualityOptions opt, std::list<std::string>& notes) const; @@ -104,6 +108,8 @@ public: } private: + std::string key_type () const; + void construct (boost::function<std::string (Channel)> get_path); 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 c7051eae..998abc07 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 "util.h" @@ -375,15 +376,14 @@ SubtitleAsset::add (shared_ptr<Subtitle> s) } void -SubtitleAsset::write_to_cpl (ostream& s) const +SubtitleAsset::write_to_cpl (xmlpp::Element* parent) const { /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */ - - s << " <MainSubtitle>\n" - << " <Id>urn:uuid:" << _uuid << "</Id>\n" - << " <AnnotationText>" << _file_name << "</AnnotationText>\n" - << " <EntryPoint>0</EntryPoint>\n" - << " </MainSubtitle>\n"; + + xmlpp::Element* main_subtitle = parent->add_child("MainSubtitle"); + main_subtitle->add_child("Id")->add_child_text("urn:uuid:" + _uuid); + main_subtitle->add_child("AnnotationText")->add_child_text(_file_name); + main_subtitle->add_child("EntryPoint")->add_child_text("0"); } struct SubtitleSorter { diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index 71ae42fc..f4a57151 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -183,7 +183,11 @@ public: SubtitleAsset (std::string directory, std::string xml_file); SubtitleAsset (std::string directory, std::string movie_title, std::string language); - void write_to_cpl (std::ostream&) const; + /** Write details of the asset to a CPL AssetList node. + * @param p Parent node. + */ + void write_to_cpl (xmlpp::Element* p) const; + virtual bool equals (boost::shared_ptr<const Asset>, EqualityOptions, std::list<std::string>& notes) const { /* XXX */ notes.push_back ("subtitle assets not compared yet"); diff --git a/src/util.cc b/src/util.cc index 6769cc41..8277b2bf 100644 --- a/src/util.cc +++ b/src/util.cc @@ -27,6 +27,11 @@ #include <iomanip> #include <boost/filesystem.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,11 +40,14 @@ #include "types.h" #include "argb_frame.h" #include "lut.h" +#include "certificates.h" using std::string; +using std::cout; using std::stringstream; using std::min; using std::max; +using std::list; using boost::shared_ptr; using namespace libdcp; @@ -91,7 +99,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); } @@ -277,3 +284,131 @@ 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"); +} + @@ -26,9 +26,14 @@ #include <openjpeg.h> #include "types.h" +namespace xmlpp { + class Element; +} + namespace libdcp { -class ARGBFrame; +class ARGBFrame; +class CertificateChain; extern std::string make_uuid (); extern std::string make_digest (std::string filename); @@ -38,4 +43,10 @@ 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); +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); + } diff --git a/src/wscript b/src/wscript index 04656976..0922cb56 100644 --- a/src/wscript +++ b/src/wscript @@ -7,12 +7,14 @@ def build(bld): obj.name = 'libdcp' obj.target = 'dcp' obj.export_includes = ['.'] - obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 OPENSSL SIGC++ LIBXML++ OPENJPEG' + obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG XMLSEC1' obj.use = 'libkumu-libdcp libasdcp-libdcp' obj.source = """ asset.cc asset_map.cc + certificates.cc cpl_file.cc + crypt_chain.cc dcp.cc dcp_time.cc lut.cc @@ -35,6 +37,8 @@ def build(bld): headers = """ asset.h + certificates.h + crypt_chain.h dcp.h dcp_time.h exceptions.h @@ -49,6 +53,7 @@ def build(bld): subtitle_asset.h test_mode.h types.h + util.h version.h xml.h """ |
