summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asset.cc18
-rw-r--r--src/asset.h16
-rw-r--r--src/certificates.cc189
-rw-r--r--src/certificates.h80
-rw-r--r--src/crypt_chain.cc140
-rw-r--r--src/crypt_chain.h6
-rw-r--r--src/dcp.cc272
-rw-r--r--src/dcp.h33
-rw-r--r--src/dcp_time.cc11
-rw-r--r--src/dcp_time.h3
-rw-r--r--src/mxf_asset.cc50
-rw-r--r--src/mxf_asset.h18
-rw-r--r--src/picture_asset.cc57
-rw-r--r--src/picture_asset.h21
-rw-r--r--src/reel.cc17
-rw-r--r--src/reel.h6
-rw-r--r--src/sound_asset.cc50
-rw-r--r--src/sound_asset.h14
-rw-r--r--src/subtitle_asset.cc14
-rw-r--r--src/subtitle_asset.h6
-rw-r--r--src/util.cc137
-rw-r--r--src/util.h13
-rw-r--r--src/wscript7
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);
+
+}
diff --git a/src/dcp.cc b/src/dcp.cc
index 6c626939..b3048bae 100644
--- a/src/dcp.cc
+++ b/src/dcp.cc
@@ -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;
+}
diff --git a/src/dcp.h b/src/dcp.h
index c5734542..5a915019 100644
--- a/src/dcp.h
+++ b/src/dcp.h
@@ -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"
diff --git a/src/reel.h b/src/reel.h
index c7d3b164..0ad5ace2 100644
--- a/src/reel.h
+++ b/src/reel.h
@@ -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");
+}
+
diff --git a/src/util.h b/src/util.h
index 721d1138..ddc5a322 100644
--- a/src/util.h
+++ b/src/util.h
@@ -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
"""