summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/make_kdm.sh7
-rw-r--r--src/certificates.cc8
-rw-r--r--src/certificates.h1
-rw-r--r--src/dcp.cc4
-rw-r--r--src/dcp.h4
-rw-r--r--src/kdm.cc95
-rw-r--r--src/kdm.h11
-rw-r--r--src/signer.cc2
-rw-r--r--src/sound_asset.h4
-rw-r--r--src/util.cc1
-rw-r--r--src/wscript2
-rw-r--r--src/xml/kdm_smpte.h23
-rw-r--r--test/encryption_test.cc9
-rw-r--r--test/kdm_key_test.cc49
-rw-r--r--test/round_trip_test.cc13
-rw-r--r--test/wscript1
16 files changed, 198 insertions, 36 deletions
diff --git a/doc/make_kdm.sh b/doc/make_kdm.sh
index 085fd2fd..60ea001f 100644
--- a/doc/make_kdm.sh
+++ b/doc/make_kdm.sh
@@ -1,9 +1,10 @@
#!/bin/bash
CINEMASLIDES=/home/carl/src/digital_cinema_tools/cinemaslides
-CPL=/home/carl/src/libdcp-test/TONEPLATES-SMPTE-ENCRYPTED_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV/cpl_eece17de-77e8-4a55-9347-b6bab5724b9f_.xml
+#CPL=/home/carl/src/libdcp-test/TONEPLATES-SMPTE-ENCRYPTED_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV/cpl_eece17de-77e8-4a55-9347-b6bab5724b9f_.xml
+CPL=/home/carl/DCP/kdmtest/KDMTEST_TST-1_F_51_2K_20130928/fbb1d2ce-fcd9-4765-8f01-2afcad274506_cpl.xml
ls $CPL
-export CINEMACERTSTORE=chain
-$CINEMASLIDES -v debug --kdm --cpl $CPL --start 2013-07-06T20:04:58+00:00 --end 2023-07-02T20:04:56+00:00 --target target.pem --keysdir content_keys --formulation t1
+export CINEMACERTSTORE=/home/carl/.config/dcpomatic/crypt
+$CINEMASLIDES -v debug --kdm --cpl $CPL --start 2013-09-28T01:41:51+00:00 --end 2014-09-28T01:41:51+00:00 --target target.pem --keysdir content_keys
diff --git a/src/certificates.cc b/src/certificates.cc
index 4c41ebba..f23dbc5d 100644
--- a/src/certificates.cc
+++ b/src/certificates.cc
@@ -194,6 +194,14 @@ Certificate::subject () const
}
string
+Certificate::common_name () const
+{
+ assert (_certificate);
+
+ return get_name_part (X509_get_subject_name (_certificate), NID_commonName);
+}
+
+string
Certificate::serial () const
{
assert (_certificate);
diff --git a/src/certificates.h b/src/certificates.h
index 198773f1..2bf8d0db 100644
--- a/src/certificates.h
+++ b/src/certificates.h
@@ -58,6 +58,7 @@ public:
std::string issuer () const;
std::string serial () const;
std::string subject () const;
+ std::string common_name () const;
/** @return RSA public key from this Certificate. Caller must not free the returned value. */
RSA* public_key () const;
diff --git a/src/dcp.cc b/src/dcp.cc
index 03d1598d..48ee7c56 100644
--- a/src/dcp.cc
+++ b/src/dcp.cc
@@ -67,7 +67,7 @@ DCP::DCP (boost::filesystem::path directory)
}
void
-DCP::write_xml (bool interop, XMLMetadata const & metadata, shared_ptr<Signer> signer) const
+DCP::write_xml (bool interop, XMLMetadata const & metadata, shared_ptr<const Signer> signer) const
{
for (list<shared_ptr<CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
(*i)->write_xml (interop, metadata, signer);
@@ -81,7 +81,7 @@ DCP::write_xml (bool interop, XMLMetadata const & metadata, shared_ptr<Signer> s
}
std::string
-DCP::write_pkl (string pkl_uuid, bool interop, XMLMetadata const & metadata, shared_ptr<Signer> signer) const
+DCP::write_pkl (string pkl_uuid, bool interop, XMLMetadata const & metadata, shared_ptr<const Signer> signer) const
{
assert (!_cpls.empty ());
diff --git a/src/dcp.h b/src/dcp.h
index f2a261cd..635a972d 100644
--- a/src/dcp.h
+++ b/src/dcp.h
@@ -83,7 +83,7 @@ public:
/** Write the required XML files to the directory that was
* passed into the constructor.
*/
- void write_xml (bool interop, XMLMetadata const &, boost::shared_ptr<Signer> signer = boost::shared_ptr<Signer> ()) const;
+ void write_xml (bool interop, XMLMetadata const &, boost::shared_ptr<const Signer> signer = boost::shared_ptr<const Signer> ()) const;
/** Compare this DCP with another, according to various options.
* @param other DCP to compare this one to.
@@ -126,7 +126,7 @@ private:
/** Write the PKL file.
* @param pkl_uuid UUID to use.
*/
- std::string write_pkl (std::string pkl_uuid, bool, XMLMetadata const &, boost::shared_ptr<Signer>) const;
+ std::string write_pkl (std::string pkl_uuid, bool, XMLMetadata const &, boost::shared_ptr<const Signer>) const;
/** Write the VOLINDEX file */
void write_volindex () const;
diff --git a/src/kdm.cc b/src/kdm.cc
index fc0aaa75..80d64441 100644
--- a/src/kdm.cc
+++ b/src/kdm.cc
@@ -46,7 +46,7 @@ using boost::shared_ptr;
using namespace libdcp;
KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key)
- : xml_kdm (new xml::DCinemaSecurityMessage (kdm))
+ : _xml_kdm (new xml::DCinemaSecurityMessage (kdm))
{
/* Read the private key */
@@ -63,7 +63,7 @@ KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key)
/* Use it to decrypt the keys */
- list<string> encrypted_keys = xml_kdm->authenticated_private.encrypted_keys;
+ list<string> encrypted_keys = _xml_kdm->authenticated_private.encrypted_keys;
for (list<string>::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
@@ -91,9 +91,9 @@ KDM::KDM (
boost::posix_time::ptime not_valid_before, boost::posix_time::ptime not_valid_after,
string annotation_text, string issue_date
)
- : xml_kdm (new xml::DCinemaSecurityMessage)
+ : _xml_kdm (new xml::DCinemaSecurityMessage)
{
- xml::AuthenticatedPublic& apu = xml_kdm->authenticated_public;
+ xml::AuthenticatedPublic& apu = _xml_kdm->authenticated_public;
/* AuthenticatedPublic */
@@ -107,13 +107,18 @@ KDM::KDM (
apu.recipient.x509_issuer_serial.x509_serial_number = recipient_cert->serial ();
apu.recipient.x509_subject_name = recipient_cert->subject ();
apu.composition_playlist_id = "urn:uuid:" + cpl->id ();
+// apu.content_authenticator = signer->certificates().leaf()->thumbprint ();
apu.content_title_text = cpl->name ();
apu.content_keys_not_valid_before = ptime_to_string (not_valid_before);
apu.content_keys_not_valid_after = ptime_to_string (not_valid_after);
apu.authorized_device_info.device_list_identifier = "urn:uuid:" + make_uuid ();
- apu.authorized_device_info.device_list_description = recipient_cert->subject ();
+ string n = recipient_cert->common_name ();
+ if (n.find (".") != string::npos) {
+ n = n.substr (n.find (".") + 1);
+ }
+ apu.authorized_device_info.device_list_description = n;
apu.authorized_device_info.device_list.push_back (recipient_cert->thumbprint ());
-
+
list<shared_ptr<const Asset> > assets = cpl->assets ();
for (list<shared_ptr<const Asset> >::iterator i = assets.begin(); i != assets.end(); ++i) {
/* XXX: non-MXF assets? */
@@ -132,36 +137,63 @@ KDM::KDM (
/* XXX: non-MXF assets? */
shared_ptr<const MXFAsset> mxf = boost::dynamic_pointer_cast<const MXFAsset> (*i);
if (mxf) {
- xml_kdm->authenticated_private.encrypted_keys.push_back (
- KDMKey (
+ KDMKey kkey (
signer, cpl->id (), mxf->key_type (), mxf->key_id (),
not_valid_before, not_valid_after, mxf->key().get()
- ).encrypted_base64 (recipient_cert)
);
+
+ _keys.push_back (kkey);
+ _xml_kdm->authenticated_private.encrypted_keys.push_back (kkey.encrypted_base64 (recipient_cert));
}
}
/* Signature */
- shared_ptr<xmlpp::Document> doc = xml_kdm->as_xml ();
+ shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
shared_ptr<cxml::Node> root (new cxml::Node (doc->get_root_node ()));
xmlpp::Node* signature = root->node_child("Signature")->node();
signer->add_signature_value (signature, "ds");
- xml_kdm->signature = xml::Signature (shared_ptr<cxml::Node> (new cxml::Node (signature)));
+ _xml_kdm->signature = xml::Signature (shared_ptr<cxml::Node> (new cxml::Node (signature)));
}
+KDM::KDM (KDM const & other)
+ : _keys (other._keys)
+ , _xml_kdm (new xml::DCinemaSecurityMessage (*other._xml_kdm.get()))
+{
+
+}
+
+KDM &
+KDM::operator= (KDM const & other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ _keys = other._keys;
+ _xml_kdm.reset (new xml::DCinemaSecurityMessage (*other._xml_kdm.get ()));
+
+ return *this;
+}
+
void
KDM::as_xml (boost::filesystem::path path) const
{
- shared_ptr<xmlpp::Document> doc = xml_kdm->as_xml ();
- doc->write_to_file_formatted (path.string(), "UTF-8");
+ shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
+ /* This must *not* be the _formatted version, otherwise the signature
+ will be wrong.
+ */
+ doc->write_to_file (path.string(), "UTF-8");
}
string
KDM::as_xml () const
{
- shared_ptr<xmlpp::Document> doc = xml_kdm->as_xml ();
- return doc->write_to_string_formatted ("UTF-8");
+ shared_ptr<xmlpp::Document> doc = _xml_kdm->as_xml ();
+ /* This must *not* be the _formatted version, otherwise the signature
+ will be wrong.
+ */
+ return doc->write_to_string ("UTF-8");
}
KDMKey::KDMKey (
@@ -225,7 +257,7 @@ KDMKey::operator= (KDMKey const & other)
if (&other == this) {
return *this;
}
-
+
_cpl_id = other._cpl_id;
_key_type = other._key_type;
_key_id = other._key_id;
@@ -269,7 +301,17 @@ KDMKey::encrypted_base64 (shared_ptr<const Certificate> recipient_cert) const
/* Lazy overallocation */
char out[encrypted_len * 2];
- return Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2);
+ Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2);
+ int const N = strlen (out);
+ stringstream lines;
+ for (int i = 0; i < N; ++i) {
+ if (i > 0 && (i % 64) == 0) {
+ lines << "\n";
+ }
+ lines << out[i];
+ }
+
+ return lines.str ();
}
string
@@ -329,8 +371,25 @@ KDMKey::put_uuid (uint8_t ** d, string id) const
stringstream s;
s << id[i] << id[i + 1];
int h;
- s >> h;
+ s >> hex >> h;
**d = h;
(*d)++;
}
}
+
+bool
+libdcp::operator== (libdcp::KDMKey const & a, libdcp::KDMKey const & b)
+{
+ if (memcmp (a._signer_thumbprint, b._signer_thumbprint, 20) != 0) {
+ return false;
+ }
+
+ return (
+ a._cpl_id == b._cpl_id &&
+ a._key_type == b._key_type &&
+ a._key_id == b._key_id &&
+ a._not_valid_before == b._not_valid_before &&
+ a._not_valid_after == b._not_valid_after &&
+ a._key == b._key
+ );
+}
diff --git a/src/kdm.h b/src/kdm.h
index 597088ed..4e897ca8 100644
--- a/src/kdm.h
+++ b/src/kdm.h
@@ -30,6 +30,8 @@
#include "key.h"
#include "metadata.h"
+class kdm_key_test;
+
namespace libdcp {
namespace xml {
@@ -114,12 +116,16 @@ public:
std::string encrypted_base64 (boost::shared_ptr<const Certificate> cert) const;
private:
+ friend class ::kdm_key_test;
+
void get (uint8_t *, uint8_t const **, int) const;
std::string get (uint8_t const **, int) const;
std::string get_uuid (uint8_t const **) const;
void put (uint8_t **, uint8_t const *, int) const;
void put (uint8_t **, std::string) const;
void put_uuid (uint8_t **, std::string) const;
+
+ friend bool operator== (KDMKey const &, KDMKey const &);
uint8_t _signer_thumbprint[20];
std::string _cpl_id;
@@ -164,6 +170,9 @@ public:
std::string annotation_text, std::string issue_date
);
+ KDM (KDM const &);
+ KDM & operator= (KDM const &);
+
/** @return The unencrypted content keys from this KDM */
std::list<KDMKey> keys () const {
return _keys;
@@ -184,7 +193,7 @@ private:
std::list<KDMKey> _keys;
/** The KDM's contents, mapped 1:1-ish to the XML */
- boost::shared_ptr<xml::DCinemaSecurityMessage> xml_kdm;
+ boost::shared_ptr<xml::DCinemaSecurityMessage> _xml_kdm;
};
diff --git a/src/signer.cc b/src/signer.cc
index 9aebd39d..f15f5325 100644
--- a/src/signer.cc
+++ b/src/signer.cc
@@ -21,12 +21,14 @@
#include <xmlsec/xmldsig.h>
#include <xmlsec/dl.h>
#include <xmlsec/app.h>
+#include <xmlsec/crypto.h>
#include <libcxml/cxml.h>
#include "signer.h"
#include "exceptions.h"
using std::string;
using std::list;
+using std::cout;
using boost::shared_ptr;
using namespace libdcp;
diff --git a/src/sound_asset.h b/src/sound_asset.h
index d41b72d5..67d3e445 100644
--- a/src/sound_asset.h
+++ b/src/sound_asset.h
@@ -88,6 +88,10 @@ public:
return _channels;
}
+ void set_sampling_rate (int s) {
+ _sampling_rate = s;
+ }
+
int sampling_rate () const {
return _sampling_rate;
}
diff --git a/src/util.cc b/src/util.cc
index 4bcc61fb..11052df6 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -33,6 +33,7 @@
#include <xmlsec/xmldsig.h>
#include <xmlsec/dl.h>
#include <xmlsec/app.h>
+#include <xmlsec/crypto.h>
#include "KM_util.h"
#include "KM_fileio.h"
#include "AS_DCP.h"
diff --git a/src/wscript b/src/wscript
index d7e118c7..1d694ec3 100644
--- a/src/wscript
+++ b/src/wscript
@@ -67,6 +67,7 @@ def build(bld):
lut.h
lut_cache.h
metadata.h
+ mono_picture_asset.h
mono_picture_frame.h
mxf_asset.h
picture_asset.h
@@ -80,6 +81,7 @@ def build(bld):
sound_asset.h
sound_frame.h
srgb_linearised_gamma_lut.h
+ stereo_picture_asset.h
stereo_picture_frame.h
subtitle_asset.h
types.h
diff --git a/src/xml/kdm_smpte.h b/src/xml/kdm_smpte.h
index 0139876f..32a297f8 100644
--- a/src/xml/kdm_smpte.h
+++ b/src/xml/kdm_smpte.h
@@ -170,6 +170,7 @@ public:
c = c->node_child ("KDMRequiredExtensions");
recipient = Recipient (c->node_child ("Recipient"));
composition_playlist_id = c->string_child ("CompositionPlaylistId");
+ content_authenticator = c->optional_string_child ("ContentAuthenticator");
content_title_text = c->string_child ("ContentTitleText");
content_keys_not_valid_before = c->string_child ("ContentKeysNotValidBefore");
content_keys_not_valid_after = c->string_child ("ContentKeysNotValidAfter");
@@ -209,6 +210,9 @@ public:
recipient.as_xml (kdm_required_extensions->add_child ("Recipient"));
kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text (composition_playlist_id);
+ if (content_authenticator) {
+ kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
+ }
kdm_required_extensions->add_child("ContentTitleText")->add_child_text (content_title_text);
kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text (content_keys_not_valid_before);
kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text (content_keys_not_valid_after);
@@ -234,6 +238,7 @@ public:
Signer signer;
Recipient recipient;
std::string composition_playlist_id;
+ boost::optional<std::string> content_authenticator;
std::string content_title_text;
std::string content_keys_not_valid_before;
std::string content_keys_not_valid_after;
@@ -312,16 +317,12 @@ public:
node->done ();
}
- void as_xml (Writer& writer, xmlpp::Element* node) const
+ void as_xml (xmlpp::Element* node) const
{
xmlpp::Element* reference = node->add_child ("Reference", "ds");
reference->set_attribute ("URI", uri);
reference->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
reference->add_child("DigestValue", "ds")->add_child_text (digest_value);
-
- if (!uri.empty ()) {
- xmlAddID (0, writer.document->cobj(), (const xmlChar *) uri.substr(1).c_str(), writer.references[uri.substr(1)]->cobj ());
- }
}
std::string uri;
@@ -359,14 +360,14 @@ public:
node->done ();
}
- void as_xml (Writer& writer, xmlpp::Element* node) const
+ void as_xml (xmlpp::Element* node) const
{
xmlpp::Element* si = node->add_child ("SignedInfo", "ds");
si->add_child ("CanonicalizationMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments");
si->add_child ("SignatureMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
- authenticated_public.as_xml (writer, si);
- authenticated_private.as_xml (writer, si);
+ authenticated_public.as_xml (si);
+ authenticated_private.as_xml (si);
node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
@@ -408,7 +409,11 @@ public:
authenticated_public.as_xml (writer, root->add_child ("AuthenticatedPublic"));
authenticated_private.as_xml (writer, root->add_child ("AuthenticatedPrivate"));
- signature.as_xml (writer, root->add_child ("Signature", "ds"));
+ signature.as_xml (root->add_child ("Signature", "ds"));
+
+ for (std::map<std::string, xmlpp::Attribute*>::const_iterator i = writer.references.begin(); i != writer.references.end(); ++i) {
+ xmlAddID (0, writer.document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
+ }
return writer.document;
}
diff --git a/test/encryption_test.cc b/test/encryption_test.cc
index e2bf9698..c079acbe 100644
--- a/test/encryption_test.cc
+++ b/test/encryption_test.cc
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE (encryption)
shared_ptr<libdcp::Signer> signer (
new libdcp::Signer (
chain,
- "test/data/signer.key"
+ "build/test/signer/leaf.key"
)
);
@@ -111,4 +111,11 @@ BOOST_AUTO_TEST_CASE (encryption)
kdm.as_xml ("build/test/bar.kdm.xml");
system ("xmllint --path schema --nonet --noout --schema schema/SMPTE-430-1-2006-Amd-1-2009-KDM.xsd build/test/bar.kdm.xml");
+ system ("xmlsec1 verify "
+ "--pubkey-cert-pem build/test/signer/leaf.signed.pem "
+ "--trusted-pem build/test/signer/intermediate.signed.pem "
+ "--trusted-pem build/test/signer/ca.self-signed.pem "
+ "--id-attr:Id http://www.smpte-ra.org/schemas/430-3/2006/ETM:AuthenticatedPublic "
+ "--id-attr:Id http://www.smpte-ra.org/schemas/430-3/2006/ETM:AuthenticatedPrivate "
+ "build/test/bar.kdm.xml");
}
diff --git a/test/kdm_key_test.cc b/test/kdm_key_test.cc
new file mode 100644
index 00000000..05b85312
--- /dev/null
+++ b/test/kdm_key_test.cc
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2013 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 <boost/test/unit_test.hpp>
+#include "kdm.h"
+
+BOOST_AUTO_TEST_CASE (kdm_key_test)
+{
+ uint8_t foo[138];
+ memset (foo, 0, 138);
+ libdcp::KDMKey kkey (foo, 138);
+
+ uint8_t* raw = new uint8_t[16];
+ uint8_t* p = raw;
+ kkey.put_uuid (&p, "5d51e8a1-b2a5-4da6-9b66-4615c3609440");
+ BOOST_CHECK_EQUAL (raw[0], 0x5d);
+ BOOST_CHECK_EQUAL (raw[1], 0x51);
+ BOOST_CHECK_EQUAL (raw[2], 0xe8);
+ BOOST_CHECK_EQUAL (raw[3], 0xa1);
+ BOOST_CHECK_EQUAL (raw[4], 0xb2);
+ BOOST_CHECK_EQUAL (raw[5], 0xa5);
+ BOOST_CHECK_EQUAL (raw[6], 0x4d);
+ BOOST_CHECK_EQUAL (raw[7], 0xa6);
+ BOOST_CHECK_EQUAL (raw[8], 0x9b);
+ BOOST_CHECK_EQUAL (raw[9], 0x66);
+ BOOST_CHECK_EQUAL (raw[10], 0x46);
+ BOOST_CHECK_EQUAL (raw[11], 0x15);
+ BOOST_CHECK_EQUAL (raw[12], 0xc3);
+ BOOST_CHECK_EQUAL (raw[13], 0x60);
+ BOOST_CHECK_EQUAL (raw[14], 0x94);
+ BOOST_CHECK_EQUAL (raw[15], 0x40);
+ delete[] raw;
+}
diff --git a/test/round_trip_test.cc b/test/round_trip_test.cc
index 2747a71f..fe720142 100644
--- a/test/round_trip_test.cc
+++ b/test/round_trip_test.cc
@@ -31,6 +31,7 @@
#include "argb_frame.h"
#include "signer_chain.h"
+using std::list;
using boost::shared_ptr;
/* Build an encrypted picture MXF and a KDM for it and check that the KDM can be decrypted */
@@ -86,6 +87,18 @@ BOOST_AUTO_TEST_CASE (round_trip_test)
/* Reload the KDM, using our private key to decrypt it */
libdcp::KDM kdm_B (kdm_file, "build/test/signer/leaf.key");
+ /* Check that the decrypted KDMKeys are the same as the ones we started with */
+ BOOST_CHECK_EQUAL (kdm_A.keys().size(), kdm_B.keys().size());
+ list<libdcp::KDMKey> keys_A = kdm_A.keys ();
+ list<libdcp::KDMKey> keys_B = kdm_B.keys ();
+ list<libdcp::KDMKey>::const_iterator i = keys_A.begin();
+ list<libdcp::KDMKey>::const_iterator j = keys_B.begin();
+ while (i != keys_A.end ()) {
+ BOOST_CHECK (*i == *j);
+ ++i;
+ ++j;
+ }
+
/* Reload the picture MXF */
shared_ptr<libdcp::MonoPictureAsset> asset_B (
new libdcp::MonoPictureAsset (work_dir, "video.mxf")
diff --git a/test/wscript b/test/wscript
index 1b2a1055..6b8b78d0 100644
--- a/test/wscript
+++ b/test/wscript
@@ -22,6 +22,7 @@ def build(bld):
obj.use = 'libdcp'
obj.source = """
test.cc
+ kdm_key_test.cc
certificates_test.cc
dcp_test.cc
encryption_test.cc