summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README14
-rw-r--r--examples/make_dcp.cc4
-rwxr-xr-xrun-tests.sh16
-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
-rw-r--r--test/data/certificate_chain79
-rw-r--r--test/data/signer.key27
-rw-r--r--test/ref/DCP/bar/402c5a88-2512-4465-9c0b-cfa687dbc5d0_pkl.xml30
-rw-r--r--test/ref/DCP/bar/63c3aece-c581-4603-b612-75e43f0c0430_cpl.xml39
-rw-r--r--test/ref/DCP/bar/ASSETMAP.xml55
-rw-r--r--test/ref/DCP/bar/VOLINDEX.xml (renamed from test/ref/DCP/VOLINDEX.xml)0
-rw-r--r--test/ref/DCP/bar/audio.mxfbin0 -> 308398 bytes
-rw-r--r--test/ref/DCP/bar/video.mxfbin0 -> 28840 bytes
-rw-r--r--test/ref/DCP/foo/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml (renamed from test/ref/DCP/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml)0
-rw-r--r--test/ref/DCP/foo/ASSETMAP.xml (renamed from test/ref/DCP/ASSETMAP.xml)0
-rw-r--r--test/ref/DCP/foo/VOLINDEX.xml4
-rw-r--r--test/ref/DCP/foo/audio.mxf (renamed from test/ref/DCP/audio.mxf)bin305326 -> 305326 bytes
-rw-r--r--test/ref/DCP/foo/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml (renamed from test/ref/DCP/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml)0
-rw-r--r--test/ref/DCP/foo/video.mxf (renamed from test/ref/DCP/video.mxf)bin26080 -> 26080 bytes
-rw-r--r--test/tests.cc119
-rw-r--r--test/wscript2
-rw-r--r--wscript13
44 files changed, 1321 insertions, 66 deletions
diff --git a/README b/README
index 0a8f3c2e..deb4b528 100644
--- a/README
+++ b/README
@@ -3,6 +3,14 @@
Hello.
+== Acknowledgements
+
+Wolfgang Woehl's cinemaslides was most informative on the
+nasty details of encryption.
+
+
+
+
== Building:
./waf configure
@@ -12,10 +20,12 @@ Hello.
== Dependencies
-boost filesystem library
-OpenSSL
+boost filesystem, signals2 and unit testing libraries
+openssl
libsigc++
libxml++
+xmlsec
+openjpeg (1.5.0 or above)
== Documentation
diff --git a/examples/make_dcp.cc b/examples/make_dcp.cc
index 0d6a8215..2bcd4fc9 100644
--- a/examples/make_dcp.cc
+++ b/examples/make_dcp.cc
@@ -75,7 +75,7 @@ main ()
for 2K projectors.
*/
boost::shared_ptr<libdcp::MonoPictureAsset> picture_asset (
- new libdcp::MonoPictureAsset (video_frame, "My Film DCP", "video.mxf", 0, 24, 48, libdcp::Size (1998, 1080))
+ new libdcp::MonoPictureAsset (video_frame, "My Film DCP", "video.mxf", 0, 24, 48, libdcp::Size (1998, 1080), false)
);
/* Now we will create a `sound asset', which is made up of a WAV file for each channel of audio. Here we're using
@@ -95,7 +95,7 @@ main ()
/* Now we can create the sound asset using these files */
boost::shared_ptr<libdcp::SoundAsset> sound_asset (
- new libdcp::SoundAsset (sound_files, "My Film DCP", "audio.mxf", 0, 24, 48)
+ new libdcp::SoundAsset (sound_files, "My Film DCP", "audio.mxf", 0, 24, 48, false)
);
/* Now that we have the assets, we can create a Reel to put them in and add it to the CPL */
diff --git a/run-tests.sh b/run-tests.sh
index 7ed965cf..97cb1931 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -1,8 +1,9 @@
-#!/bin/bash
+#!/bin/bash -e
#
-# Runs our test suite, which builds a DCP.
-# The output is compared against the one
+# Runs our test suite, which (amongst other things)
+# builds a couple of DCPs.
+# The outputs are compared against the ones
# in test/ref/DCP, and an error is given
# if anything is different.
#
@@ -16,7 +17,14 @@ elif [ "$1" == "--valgrind" ]; then
else
LD_LIBRARY_PATH=build/src:build/asdcplib/src build/test/tests
fi
-diff -ur test/ref/DCP build/test/foo
+
+diff -ur test/ref/DCP/foo build/test/foo
+if [ "$?" != "0" ]; then
+ echo "FAIL: files differ"
+ exit 1
+fi
+
+diff -ur test/ref/DCP/bar build/test/bar
if [ "$?" != "0" ]; then
echo "FAIL: files differ"
exit 1
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
diff --git a/test/data/certificate_chain b/test/data/certificate_chain
new file mode 100644
index 00000000..0aa88802
--- /dev/null
+++ b/test/data/certificate_chain
@@ -0,0 +1,79 @@
+-----BEGIN CERTIFICATE-----
+MIIEdzCCA1+gAwIBAgIBBTANBgkqhkiG9w0BAQsFADCBgjEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUt
+NDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THHJUZUs3eCtu
+b3BGa3lwaGZsb296NnAyWk03QT0wHhcNMTIxMjI2MTAyMTE4WhcNMjIxMjI0MTAy
+MTE4WjCBgjEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUu
+b3JnMS0wKwYDVQQDFCQuc21wdGUtNDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJ
+T04xJTAjBgNVBC4THHJUZUs3eCtub3BGa3lwaGZsb296NnAyWk03QT0wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWxcyyhakSyyBDBznaA0di/TYQ49k+
+mwY7s3IsGDPEuP4JpQD7sqPwcBIe0LrJJioggWo92C8rpx4Sa2WP1h31gn7g9ceU
+juFcOS2AjX60srQC3Us17o/sewKfyt2F0Fe1iB1e0qptyhH7X8yn4Z7MrRSVY8KA
+TQ4Flo649EM4u27QnH2oavfTUEmj56ZsY37aULHvK/fyF1vYHKgf5/nmBj9Nfcue
+dUAPX/uztETBhGpljlqY8tLuPL6L5ToqxYX6SCtx28N6KpBuhnRl7VuYxs5JvrHn
+2CyjZ5BU6fLz9GsEJqU8p+RVS2cFHJeDLY0GgGACRnZ+0WvgBQC7bjQzAgMBAAGj
+gfUwgfIwEgYDVR0TAQH/BAgwBgEB/wIBAzALBgNVHQ8EBAMCAQYwHQYDVR0OBBYE
+FK03iu8fp6KRZMqYX5aKM+qdmTOwMIGvBgNVHSMEgacwgaSAFK03iu8fp6KRZMqY
+X5aKM+qdmTOwoYGIpIGFMIGCMRQwEgYDVQQKEwtleGFtcGxlLm9yZzEUMBIGA1UE
+CxMLZXhhbXBsZS5vcmcxLTArBgNVBAMUJC5zbXB0ZS00MzAtMi5ST09ULk5PVF9G
+T1JfUFJPRFVDVElPTjElMCMGA1UELhMcclRlSzd4K25vcEZreXBoZmxvb3o2cDJa
+TTdBPYIBBTANBgkqhkiG9w0BAQsFAAOCAQEAeXAk+HIPX2xyt/XSwwwHVPOKehQF
+pEX945nCYLum7LjiDvYy8fV14ByLuyjzrb369huSW75+efTvLhe7MwA3aDlD0TsX
+pf5yW/TRR0CFZ2xdkvvXF00zc4ZpESGjoIB6J3pMGsLg0PoSzG/5JHuS4S96WSrJ
+StApvNTISE+DEsaSV/MEi61ZKXieA3cZwaVzGlMlHfTs5O5ggJjwsAxS6Tw7/oAO
+yvclCVyPqXSygQcPjSZXtV4DRnjMx7xU4WC05aHlRVTMNJ8KRt8f/rktYA3MV5lm
+RczqMKOEn7cHQS7i5LZWL00HPiwn3TNATlE86t5YdKZWoALrX/wAncH9vA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEfzCCA2egAwIBAgIBBjANBgkqhkiG9w0BAQsFADCBgjEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUt
+NDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THHJUZUs3eCtu
+b3BGa3lwaGZsb296NnAyWk03QT0wHhcNMTIxMjI2MTAyMTE4WhcNMjIxMjIzMTAy
+MTE4WjCBijEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUu
+b3JnMTUwMwYDVQQDFCwuc21wdGUtNDMwLTIuSU5URVJNRURJQVRFLk5PVF9GT1Jf
+UFJPRFVDVElPTjElMCMGA1UELhMcbG1xN0FJY1pvWWwyU3RJNkVvb2RnejRrR2s4
+PTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPwL+vvNvDZ78dsoF6Ov
+jgByKfsOFATQleNi5ZRWs39CLHdbYFycIjB2JXZbGZ/2GiPbHB82EOmMUN8NZFkt
+ksXttOPafYKGTFu1hQWW/b3D2v37X8QvmqMIZCBd1AFu3nDgjQjofDPrCTauPnuu
+jxH+0dBD2WLzi7a1nPDB7HaFkHegLpgOADD7XfFEvdsBXv0GjclRtnv3Sc7/6kYP
+/9vsjbzwF9CjbU/MAa/DnQvbUsW2TNAqs7nCUm+uFPqg4PKFnI0iLvnOjB18GX2u
+2U1yX2nGEF6P7LfhC9Sq1UO2e/H9bQ8H/aZRO4fl8jV457NEmNERbpmHQM8T83PF
+TMECAwEAAaOB9TCB8jASBgNVHRMBAf8ECDAGAQH/AgECMAsGA1UdDwQEAwIBBjAd
+BgNVHQ4EFgQUlmq7AIcZoYl2StI6Eoodgz4kGk8wga8GA1UdIwSBpzCBpIAUrTeK
+7x+nopFkyphflooz6p2ZM7ChgYikgYUwgYIxFDASBgNVBAoTC2V4YW1wbGUub3Jn
+MRQwEgYDVQQLEwtleGFtcGxlLm9yZzEtMCsGA1UEAxQkLnNtcHRlLTQzMC0yLlJP
+T1QuTk9UX0ZPUl9QUk9EVUNUSU9OMSUwIwYDVQQuExxyVGVLN3grbm9wRmt5cGhm
+bG9vejZwMlpNN0E9ggEFMA0GCSqGSIb3DQEBCwUAA4IBAQADsc9UuYZQRiqj1Raj
+9Z21b9KpIpqfOBcPuNK1E4bpCfKT4J9gNP+k33iG73QBL2C/7+XSfYRGwCDk4URt
+OAxK34A6SU2zyrPpaBKBc2OoyNiiA/GGhXe9X+9/HOO3OUpdqpSwAho6rFIjYjNP
+0MCoH7HXzCCM+Zl7lt9Nb4LHTjhi2OaSyj8yfxrNRjfHP2EbTSUTlwAJyfaVtLeF
+o8j8hXiGxx1tYlfDw74qdzFbymBjWNHatFm8W7j+MiT0awAqpKs6fTV3iqFA87rl
+nJN1rlcKYnmU9SDqwgivaUSViagQucgk9yzK7hof3NAITL01mHuc5huRea+ZVCRR
+68Ag
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEezCCA2OgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBijEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMTUwMwYDVQQDFCwuc21wdGUt
+NDMwLTIuSU5URVJNRURJQVRFLk5PVF9GT1JfUFJPRFVDVElPTjElMCMGA1UELhMc
+bG1xN0FJY1pvWWwyU3RJNkVvb2RnejRrR2s4PTAeFw0xMjEyMjYxMDIxMThaFw0y
+MjEyMjIxMDIxMThaMIGEMRQwEgYDVQQKEwtleGFtcGxlLm9yZzEUMBIGA1UECxML
+ZXhhbXBsZS5vcmcxLzAtBgNVBAMUJkNTLnNtcHRlLTQzMC0yLkxFQUYuTk9UX0ZP
+Ul9QUk9EVUNUSU9OMSUwIwYDVQQuExxXT1ZYRkJxd1QwNXl2WUJzbzVCSHRVbjkr
+Vjg9MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt6QreIDAQheQZVel
+n9+7z/M2ssr2iCTmcmFEJU2anRhuCUDACp9QFfT+5tcPNi7qO5mz0IoPQ8jzvYSa
+QysWGGofQiSbPMRP0J3fhP6wK4KCD4EST6zLzGQ84DaQOLJoncom4lNZrS56ugVJ
+rm3ekUoWGCZ9YfBHH1A8eWUm5yJiJN0FAuvhQojS0hBqcWbwfOJDogR2NvBSaL6k
+zVlTGPcgu+27SQ+AIMJT8V5hqmMixWc+etEYzsg7hFM8EQj3KoaPi70yJ8ELfK3y
+y1tpnOCkg9RtGWXS2eKEGR9AMI2424Z59dcbunZkE0sRzEXp+kBKyc/qIAWVrkEb
+liBXcwIDAQABo4HvMIHsMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgWgMB0GA1Ud
+DgQWBBRY5VcUGrBPTnK9gGyjkEe1Sf35XzCBrwYDVR0jBIGnMIGkgBSWarsAhxmh
+iXZK0joSih2DPiQaT6GBiKSBhTCBgjEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDAS
+BgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUtNDMwLTIuUk9PVC5O
+T1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THHJUZUs3eCtub3BGa3lwaGZsb296
+NnAyWk03QT2CAQYwDQYJKoZIhvcNAQELBQADggEBAGTvqRuLZp1fTGnAoKMeGzo3
+G+jRz26vNVKYa09RwifNLEI5V0MlAHWm4YWIj9Ml+AF7CEhYbw69/v0ygK9NXVPk
+Hl3+JBCoveaB0Fz73IQRHJVTDKlfH54aDoDFayQK6dZ1pfpvZa72ih3IXva9PsRT
+5xx4ILIatJb9JsE3zmW3fYk/93YMTq3ewUdWi7ldpwwLFVs89TNOQOcAMIwvPpYW
+kqmtwIG6as2SiV0fJ0PpcxYZN3jvsHB/wRy2f5pSlAw6NBeVZNoKX4Zxue77TrAC
+cmsEtjOEApFv+mKdmOPjB5qXurZEzgyzdm8xdKw4UnblGzA81/7xMchPK0P66To=
+-----END CERTIFICATE-----
diff --git a/test/data/signer.key b/test/data/signer.key
new file mode 100644
index 00000000..2cd376d9
--- /dev/null
+++ b/test/data/signer.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAt6QreIDAQheQZVeln9+7z/M2ssr2iCTmcmFEJU2anRhuCUDA
+Cp9QFfT+5tcPNi7qO5mz0IoPQ8jzvYSaQysWGGofQiSbPMRP0J3fhP6wK4KCD4ES
+T6zLzGQ84DaQOLJoncom4lNZrS56ugVJrm3ekUoWGCZ9YfBHH1A8eWUm5yJiJN0F
+AuvhQojS0hBqcWbwfOJDogR2NvBSaL6kzVlTGPcgu+27SQ+AIMJT8V5hqmMixWc+
+etEYzsg7hFM8EQj3KoaPi70yJ8ELfK3yy1tpnOCkg9RtGWXS2eKEGR9AMI2424Z5
+9dcbunZkE0sRzEXp+kBKyc/qIAWVrkEbliBXcwIDAQABAoIBAGHIOJ1hcP3ALlLH
+6JjIOOjxSB7Lk5nKjCo7QF3chIdBitXCdH8zdSE74r5npOHk+TPjE6vm11nwllhD
+UyCQwKMfXqWJeF9S6GzcozfdpVCFnVtEDsv95kZe2UtJwmFuHeZmzW2VlBpytUZ3
+qlQGjIwwNrOFSx3rIvO5dXnuMli/PyQqEqXE4agQj1ZSHXgc//crzRX/XWKNY+bA
+cWAVinjl+TIR6OOLLLu6X9lPU+Ai9TuhzMQCCMocR0w81fG2pflh37buAzfzdvms
+MunZL8Le4/S6fuMTMslx12AHL55PIAiLF6uApah48rGfL7sZsDEhoPQk7b1Y7pHW
+A6C7AYECgYEA3wYIT59lIhEaMgagyUfb5xX8Zk2TOq6qJwkRwiSdep2D5bL8l9eL
+63bwi5ZMxuPMCnb3bw18J6bV7e7NERjQAz7deL7WxxB68muDXw44cyBHkpgBs2i3
+Qg01tTFyUqB7LHlQ9J9ZTXoHrpgmySXfu3DLWlFCjqt0dv7qsNgEhQcCgYEA0sts
+5aHMah3uZdIdrB7nAFVShlcM96yU/+np+re1E+L5Qpr+yox7IKTWZI7WlZ11QDmb
+//qLqdGjECIcoS3jkCZKpqPzuZKWoGyUef0ixmQTccq77Xitd1pu07QoS06VydVG
+TeMqdZCchYN2NAiTK/4d1H4kpJCSNMpM/JfaizUCgYEAsbIDwzUUBk2sGnnPeDBK
+FNger4BVs2bhaZK/VHmKA90m70hqG62A7U5qID3T1JBBzYC2awRKjOlQAHDJcTrc
+2gknuwIK7LbDzw08sesJrSjl/fYhPMpNYVJXHZRVK2J0YZ4Tk6S3KZT2M/VEyfXk
+Slt3mvGt3zDa7cj0Q40KJNcCgYA8BLfI2jy9qjAKxby1GUdkjOamYXrLulPiWdPJ
+Ey13sBSQplkNitoz/Tsd/p2Sk/aihsSoKcpCW/I10cCdE9fLX1u5/sySde51VvUf
+lrekDTKMH9FKWCXr6c/Mb2tZpdJ4WUVfP+MC/l3Slg+92QMub3hG3HPKd29poIAz
+G3maUQKBgGgqigBTrmOcZuEPxcC2eei7m4wckv0pCmTbuvZ479jsoWZRkSE8jm9X
+V+ZjLRbwCCcdEky7CW/iif5Zc0WW2Z2Y86KPPApESnhkMVyZDt4sjjo9maMAxAI6
+ajdpjTszRZvJaeTYDOTZM9bPRFiMHJlAQp0+lt8zrlHUhohl2yT2
+-----END RSA PRIVATE KEY-----
diff --git a/test/ref/DCP/bar/402c5a88-2512-4465-9c0b-cfa687dbc5d0_pkl.xml b/test/ref/DCP/bar/402c5a88-2512-4465-9c0b-cfa687dbc5d0_pkl.xml
new file mode 100644
index 00000000..a87f9e27
--- /dev/null
+++ b/test/ref/DCP/bar/402c5a88-2512-4465-9c0b-cfa687dbc5d0_pkl.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PackingList xmlns="http://www.smpte-ra.org/schemas/429-8/2007/PKL">
+ <Id>urn:uuid:402c5a88-2512-4465-9c0b-cfa687dbc5d0</Id>
+ <AnnotationText>A Test DCP</AnnotationText>
+ <IssueDate>2012-07-17T04:45:18+00:00</IssueDate>
+ <Issuer>OpenDCP 0.0.25</Issuer>
+ <Creator>OpenDCP 0.0.25</Creator>
+ <AssetList>
+ <Asset>
+ <Id>urn:uuid:2b9b857f-ab4a-440e-a313-1ace0f1cfc95</Id>
+ <AnnotationText>video.mxf</AnnotationText>
+ <Hash>fTMi9Xvr8NzuRhm7LmSTk6k1HYo=</Hash>
+ <Size>28840</Size>
+ <Type>application/mxf</Type>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:aa3fb133-0d18-4083-a039-e441b0788e79</Id>
+ <AnnotationText>audio.mxf</AnnotationText>
+ <Hash>2MlsntiFrekkQvwbRPLC2XEMU78=</Hash>
+ <Size>308398</Size>
+ <Type>application/mxf</Type>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:63c3aece-c581-4603-b612-75e43f0c0430</Id>
+ <Hash>l/g+bdCKF6ofhedin5qrLcObS1E=</Hash>
+ <Size>1526</Size>
+ <Type>text/xml</Type>
+ </Asset>
+ </AssetList>
+</PackingList>
diff --git a/test/ref/DCP/bar/63c3aece-c581-4603-b612-75e43f0c0430_cpl.xml b/test/ref/DCP/bar/63c3aece-c581-4603-b612-75e43f0c0430_cpl.xml
new file mode 100644
index 00000000..43edb1b3
--- /dev/null
+++ b/test/ref/DCP/bar/63c3aece-c581-4603-b612-75e43f0c0430_cpl.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:63c3aece-c581-4603-b612-75e43f0c0430</Id>
+ <AnnotationText>A Test DCP</AnnotationText>
+ <IssueDate>2012-07-17T04:45:18+00:00</IssueDate>
+ <Creator>OpenDCP 0.0.25</Creator>
+ <ContentTitleText>A Test DCP</ContentTitleText>
+ <ContentKind>feature</ContentKind>
+ <ContentVersion>
+ <Id>urn:uri:63c3aece-c581-4603-b612-75e43f0c0430_2012-07-17T04:45:18+00:00</Id>
+ <LabelText>63c3aece-c581-4603-b612-75e43f0c0430_2012-07-17T04:45:18+00:00</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:7d861d35-c775-48e6-a4f8-fbfdbfc1556a</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:2b9b857f-ab4a-440e-a313-1ace0f1cfc95</Id>
+ <AnnotationText>video.mxf</AnnotationText>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ <FrameRate>24 1</FrameRate>
+ <ScreenAspectRatio>32 32</ScreenAspectRatio>
+ </MainPicture>
+ <MainSound>
+ <Id>urn:uuid:aa3fb133-0d18-4083-a039-e441b0788e79</Id>
+ <AnnotationText>audio.mxf</AnnotationText>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ </MainSound>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
diff --git a/test/ref/DCP/bar/ASSETMAP.xml b/test/ref/DCP/bar/ASSETMAP.xml
new file mode 100644
index 00000000..359f453f
--- /dev/null
+++ b/test/ref/DCP/bar/ASSETMAP.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AssetMap xmlns="http://www.smpte-ra.org/schemas/429-9/2007/AM">
+ <Id>urn:uuid:58f161a5-16d1-4896-93df-52a5a330082f</Id>
+ <Creator>OpenDCP 0.0.25</Creator>
+ <VolumeCount>1</VolumeCount>
+ <IssueDate>2012-07-17T04:45:18+00:00</IssueDate>
+ <Issuer>OpenDCP 0.0.25</Issuer>
+ <AssetList>
+ <Asset>
+ <Id>urn:uuid:402c5a88-2512-4465-9c0b-cfa687dbc5d0</Id>
+ <PackingList>true</PackingList>
+ <ChunkList>
+ <Chunk>
+ <Path>402c5a88-2512-4465-9c0b-cfa687dbc5d0_pkl.xml</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>1049</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:63c3aece-c581-4603-b612-75e43f0c0430</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>63c3aece-c581-4603-b612-75e43f0c0430_cpl.xml</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>1526</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:2b9b857f-ab4a-440e-a313-1ace0f1cfc95</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>video.mxf</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>28840</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:aa3fb133-0d18-4083-a039-e441b0788e79</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>audio.mxf</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>308398</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ </AssetList>
+</AssetMap>
diff --git a/test/ref/DCP/VOLINDEX.xml b/test/ref/DCP/bar/VOLINDEX.xml
index f66c004a..f66c004a 100644
--- a/test/ref/DCP/VOLINDEX.xml
+++ b/test/ref/DCP/bar/VOLINDEX.xml
diff --git a/test/ref/DCP/bar/audio.mxf b/test/ref/DCP/bar/audio.mxf
new file mode 100644
index 00000000..1188e5fc
--- /dev/null
+++ b/test/ref/DCP/bar/audio.mxf
Binary files differ
diff --git a/test/ref/DCP/bar/video.mxf b/test/ref/DCP/bar/video.mxf
new file mode 100644
index 00000000..20eb6f4a
--- /dev/null
+++ b/test/ref/DCP/bar/video.mxf
Binary files differ
diff --git a/test/ref/DCP/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml b/test/ref/DCP/foo/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml
index 59d50075..59d50075 100644
--- a/test/ref/DCP/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml
+++ b/test/ref/DCP/foo/81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml
diff --git a/test/ref/DCP/ASSETMAP.xml b/test/ref/DCP/foo/ASSETMAP.xml
index defe18da..defe18da 100644
--- a/test/ref/DCP/ASSETMAP.xml
+++ b/test/ref/DCP/foo/ASSETMAP.xml
diff --git a/test/ref/DCP/foo/VOLINDEX.xml b/test/ref/DCP/foo/VOLINDEX.xml
new file mode 100644
index 00000000..f66c004a
--- /dev/null
+++ b/test/ref/DCP/foo/VOLINDEX.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VolumeIndex xmlns="http://www.smpte-ra.org/schemas/429-9/2007/AM">
+ <Index>1</Index>
+</VolumeIndex>
diff --git a/test/ref/DCP/audio.mxf b/test/ref/DCP/foo/audio.mxf
index 9bc735af..9bc735af 100644
--- a/test/ref/DCP/audio.mxf
+++ b/test/ref/DCP/foo/audio.mxf
Binary files differ
diff --git a/test/ref/DCP/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml b/test/ref/DCP/foo/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml
index 7a8ec697..7a8ec697 100644
--- a/test/ref/DCP/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml
+++ b/test/ref/DCP/foo/df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml
diff --git a/test/ref/DCP/video.mxf b/test/ref/DCP/foo/video.mxf
index 645fb85a..645fb85a 100644
--- a/test/ref/DCP/video.mxf
+++ b/test/ref/DCP/foo/video.mxf
Binary files differ
diff --git a/test/tests.cc b/test/tests.cc
index d8b7e5d4..ad31cb86 100644
--- a/test/tests.cc
+++ b/test/tests.cc
@@ -19,6 +19,7 @@
#include <cmath>
#include <boost/filesystem.hpp>
+#include <libxml++/libxml++.h>
#include "KM_prng.h"
#include "dcp.h"
#include "util.h"
@@ -29,6 +30,8 @@
#include "picture_asset.h"
#include "sound_asset.h"
#include "reel.h"
+#include "certificates.h"
+#include "crypt_chain.h"
#include "gamma_lut.h"
#include "cpl.h"
@@ -57,6 +60,8 @@ wav (libdcp::Channel)
BOOST_AUTO_TEST_CASE (dcp_test)
{
+ libdcp::init ();
+
Kumu::libdcp_test = true;
libdcp::XMLMetadata xml_meta;
@@ -80,6 +85,7 @@ BOOST_AUTO_TEST_CASE (dcp_test)
24,
24,
libdcp::Size (32, 32),
+ false,
mxf_meta
));
@@ -90,7 +96,10 @@ BOOST_AUTO_TEST_CASE (dcp_test)
&(d.Progress),
24,
24,
+ 0,
+ 2,
2,
+ false,
mxf_meta
));
@@ -102,17 +111,17 @@ BOOST_AUTO_TEST_CASE (dcp_test)
BOOST_AUTO_TEST_CASE (error_test)
{
- libdcp::DCP d ("build/test/bar");
+ libdcp::DCP d ("build/test/fred");
vector<string> p;
p.push_back ("frobozz");
- BOOST_CHECK_THROW (new libdcp::MonoPictureAsset (p, "build/test/bar", "video.mxf", &d.Progress, 24, 24, libdcp::Size (32, 32)), libdcp::FileError);
- BOOST_CHECK_THROW (new libdcp::SoundAsset (p, "build/test/bar", "audio.mxf", &d.Progress, 24, 24), libdcp::FileError);
+ BOOST_CHECK_THROW (new libdcp::MonoPictureAsset (p, "build/test/bar", "video.mxf", &d.Progress, 24, 24, false, libdcp::Size (32, 32)), libdcp::FileError);
+ BOOST_CHECK_THROW (new libdcp::SoundAsset (p, "build/test/bar", "audio.mxf", &d.Progress, 24, 24, false), libdcp::FileError);
}
BOOST_AUTO_TEST_CASE (read_dcp)
{
- libdcp::DCP d ("test/ref/DCP");
+ libdcp::DCP d ("test/ref/DCP/foo");
d.read ();
list<shared_ptr<const libdcp::CPL> > cpls = d.cpls ();
@@ -585,6 +594,108 @@ BOOST_AUTO_TEST_CASE (color)
}
+BOOST_AUTO_TEST_CASE (encryption)
+{
+ Kumu::libdcp_test = true;
+
+ libdcp::Metadata* t = libdcp::Metadata::instance ();
+ t->issuer = "OpenDCP 0.0.25";
+ t->creator = "OpenDCP 0.0.25";
+ t->company_name = "OpenDCP";
+ t->product_name = "OpenDCP";
+ t->product_version = "0.0.25";
+ t->issue_date = "2012-07-17T04:45:18+00:00";
+ boost::filesystem::remove_all ("build/test/bar");
+ boost::filesystem::create_directories ("build/test/bar");
+ libdcp::DCP d ("build/test/bar");
+
+ libdcp::CertificateChain chain;
+ chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/ca.self-signed.pem")));
+ chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/intermediate.signed.pem")));
+ chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/leaf.signed.pem")));
+
+ shared_ptr<libdcp::Encryption> crypt (
+ new libdcp::Encryption (
+ chain,
+ "test/data/signer.key"
+ )
+ );
+
+ shared_ptr<libdcp::CPL> cpl (new libdcp::CPL ("build/test/bar", "A Test DCP", libdcp::FEATURE, 24, 24));
+
+ shared_ptr<libdcp::MonoPictureAsset> mp (new libdcp::MonoPictureAsset (
+ j2c,
+ "build/test/bar",
+ "video.mxf",
+ &d.Progress,
+ 24,
+ 24,
+ 32,
+ 32,
+ true
+ ));
+
+ shared_ptr<libdcp::SoundAsset> ms (new libdcp::SoundAsset (
+ wav,
+ "build/test/bar",
+ "audio.mxf",
+ &(d.Progress),
+ 24,
+ 24,
+ 0,
+ 2,
+ true
+ ));
+
+ cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (mp, ms, shared_ptr<libdcp::SubtitleAsset> ())));
+ d.add_cpl (cpl);
+
+ d.write_xml (crypt);
+
+ shared_ptr<xmlpp::Document> kdm = cpl->make_kdm (
+ crypt->certificates,
+ crypt->signer_key,
+ crypt->certificates.leaf(),
+ boost::posix_time::time_from_string ("2013-01-01 00:00:00"),
+ boost::posix_time::time_from_string ("2013-01-08 00:00:00")
+ );
+
+ kdm->write_to_file_formatted ("build/test/bar.kdm.xml", "UTF-8");
+}
+
+BOOST_AUTO_TEST_CASE (certificates)
+{
+ libdcp::CertificateChain c;
+
+ c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/ca.self-signed.pem")));
+ c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/intermediate.signed.pem")));
+ c.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate ("test/data/leaf.signed.pem")));
+
+ BOOST_CHECK_EQUAL (
+ c.root()->issuer(),
+ "/O=example.org/OU=example.org/CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION/dnQualifier=rTeK7x+nopFkyphflooz6p2ZM7A="
+ );
+
+ BOOST_CHECK_EQUAL (
+ libdcp::Certificate::name_for_xml (c.root()->issuer()),
+ "dnQualifier=rTeK7x\\+nopFkyphflooz6p2ZM7A=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
+ );
+
+ BOOST_CHECK_EQUAL (c.root()->serial(), "5");
+
+ BOOST_CHECK_EQUAL (
+ libdcp::Certificate::name_for_xml (c.root()->subject()),
+ "dnQualifier=rTeK7x\\+nopFkyphflooz6p2ZM7A=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
+ );
+}
+
+BOOST_AUTO_TEST_CASE (crypt_chain)
+{
+ boost::filesystem::remove_all ("build/test/crypt");
+ boost::filesystem::create_directory ("build/test/crypt");
+ libdcp::make_crypt_chain ("build/test/crypt");
+}
+
BOOST_AUTO_TEST_CASE (recovery)
{
Kumu::libdcp_test = true;
diff --git a/test/wscript b/test/wscript
index 4a2f60a5..2892bf9e 100644
--- a/test/wscript
+++ b/test/wscript
@@ -18,7 +18,7 @@ def configure(conf):
def build(bld):
obj = bld(features = 'cxx cxxprogram')
obj.name = 'tests'
- obj.uselib = 'BOOST_TEST OPENJPEG CXML'
+ obj.uselib = 'BOOST_TEST OPENJPEG CXML XMLSEC1'
obj.use = 'libdcp'
obj.source = 'tests.cc'
obj.target = 'tests'
diff --git a/wscript b/wscript
index 6f421cf6..80044104 100644
--- a/wscript
+++ b/wscript
@@ -31,6 +31,10 @@ def configure(conf):
conf.check_cfg(package = 'openssl', args = '--cflags --libs', uselib_store = 'OPENSSL', mandatory = True)
conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'LIBXML++', mandatory = True)
+ conf.check_cfg(package = 'xmlsec1', args = '--cflags --libs', uselib_store = 'XMLSEC1', mandatory = True)
+ # Remove erroneous escaping of quotes from xmlsec1 defines
+ conf.env.DEFINES_XMLSEC1 = [f.replace('\\', '') for f in conf.env.DEFINES_XMLSEC1]
+
if conf.options.static:
conf.check_cc(fragment = """
#include <stdio.h>\n
@@ -88,6 +92,15 @@ def configure(conf):
msg = 'Checking for boost signals2 library',
uselib_store = 'BOOST_SIGNALS2')
+ conf.check_cxx(fragment = """
+ #include <boost/date_time.hpp>\n
+ int main() { boost::gregorian::day_clock::local_day(); }\n
+ """,
+ msg = 'Checking for boost datetime library',
+ libpath = '/usr/local/lib',
+ lib = ['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
+ uselib_store = 'BOOST_DATETIME')
+
conf.recurse('test')
conf.recurse('asdcplib')