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