summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2013-07-06 23:31:16 +0100
committerCarl Hetherington <cth@carlh.net>2013-07-06 23:31:16 +0100
commit4313456938d34d93239194e914b82e7a5ae14c1c (patch)
tree622dffc47d90a14b70eecd35b0e5717a9b80fade
parent3340b9a66ca2153daf8568ae147abcc9c5983323 (diff)
Can decrypt a KDM.
-rw-r--r--src/kdm.cc154
-rw-r--r--src/kdm.h92
-rw-r--r--src/wscript5
-rw-r--r--test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml192
-rw-r--r--test/data/private.key27
-rw-r--r--test/kdm_test.cc44
-rw-r--r--test/tests.cc1
7 files changed, 513 insertions, 2 deletions
diff --git a/src/kdm.cc b/src/kdm.cc
new file mode 100644
index 00000000..14a351e1
--- /dev/null
+++ b/src/kdm.cc
@@ -0,0 +1,154 @@
+/*
+ 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 <iomanip>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <libcxml/cxml.h>
+#include "KM_util.h"
+#include "kdm.h"
+#include "exceptions.h"
+
+using std::list;
+using std::string;
+using std::stringstream;
+using std::hex;
+using std::setw;
+using std::setfill;
+using boost::shared_ptr;
+using namespace libdcp;
+
+KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key)
+{
+ /* Read the private key */
+
+ FILE* private_key_file = fopen (private_key.string().c_str(), "r");
+ if (!private_key_file) {
+ throw FileError ("could not find RSA private key file", private_key.string ());
+ }
+
+ RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0);
+ fclose (private_key_file);
+ if (!rsa) {
+ throw FileError ("could not read RSA private key file", private_key.string ());
+ }
+
+
+ /* Read the KDM, decrypting it */
+
+ cxml::File f (kdm.string (), "DCinemaSecurityMessage");
+
+ shared_ptr<cxml::Node> authenticated_private = f.node_child ("AuthenticatedPrivate");
+ list<shared_ptr<cxml::Node> > encrypted_keys = authenticated_private->node_children ("EncryptedKey");
+
+ for (list<shared_ptr<cxml::Node> >::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
+
+ /* Get the base-64-encoded cipher value from the KDM */
+ shared_ptr<cxml::Node> cipher_data = (*i)->node_child ("CipherData");
+ shared_ptr<cxml::Node> cipher_value_base64 = cipher_data->node_child ("CipherValue");
+
+ /* Decode it from base-64 */
+ unsigned char cipher_value[256];
+ ui32_t cipher_value_len;
+ if (Kumu::base64decode (cipher_value_base64->content().c_str(), cipher_value, sizeof (cipher_value), &cipher_value_len)) {
+ RSA_free (rsa);
+ throw MiscError ("could not base-64-decode CipherValue from KDM");
+ }
+
+ /* Decrypt it */
+ unsigned char decrypted[256];
+ unsigned int const decrypted_len = RSA_private_decrypt (cipher_value_len, cipher_value, decrypted, rsa, RSA_PKCS1_OAEP_PADDING);
+ assert (decrypted_len < sizeof (decrypted));
+
+ _ciphers.push_back (KDMCipher (decrypted, decrypted_len));
+ }
+
+ RSA_free (rsa);
+}
+
+
+KDMCipher::KDMCipher (unsigned char const * raw, int len)
+{
+ switch (len) {
+ case 134:
+ /* interop */
+ _structure_id = get (&raw, 16);
+ _signer_thumbprint = get (&raw, 20);
+ _cpl_id = get_uuid (&raw, 16);
+ _key_id = get_uuid (&raw, 16);
+ _not_valid_before = get (&raw, 25);
+ _not_valid_after = get (&raw, 25);
+ _key_data = get_hex (&raw, 16);
+ break;
+ case 138:
+ /* SMPTE */
+ _structure_id = get (&raw, 16);
+ _signer_thumbprint = get (&raw, 20);
+ _cpl_id = get_uuid (&raw, 16);
+ _key_type = get (&raw, 4);
+ _key_id = get_uuid (&raw, 16);
+ _not_valid_before = get (&raw, 25);
+ _not_valid_after = get (&raw, 25);
+ _key_data = get_hex (&raw, 16);
+ break;
+ default:
+ assert (false);
+ }
+}
+
+string
+KDMCipher::get (unsigned char const ** p, int N) const
+{
+ string g;
+ for (int i = 0; i < N; ++i) {
+ g += **p;
+ (*p)++;
+ }
+
+ return g;
+}
+
+string
+KDMCipher::get_uuid (unsigned char const ** p, int N) const
+{
+ stringstream g;
+
+ for (int i = 0; i < N; ++i) {
+ g << setw(2) << setfill('0') << hex << static_cast<int> (**p);
+ (*p)++;
+ if (i == 3 || i == 5 || i == 7 || i == 9) {
+ g << '-';
+ }
+ }
+
+ return g.str ();
+}
+
+string
+KDMCipher::get_hex (unsigned char const ** p, int N) const
+{
+ stringstream g;
+
+ for (int i = 0; i < N; ++i) {
+ g << setw(2) << setfill('0') << hex << static_cast<int> (**p);
+ (*p)++;
+ }
+
+ return g.str ();
+}
diff --git a/src/kdm.h b/src/kdm.h
new file mode 100644
index 00000000..0b450987
--- /dev/null
+++ b/src/kdm.h
@@ -0,0 +1,92 @@
+/*
+ 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/filesystem.hpp>
+
+namespace libdcp {
+
+class KDMCipher
+{
+public:
+ KDMCipher (unsigned char const *, int);
+
+ std::string structure_id () const {
+ return _structure_id;
+ }
+
+ std::string signer_thumbprint () const {
+ return _signer_thumbprint;
+ }
+
+ std::string cpl_id () const {
+ return _cpl_id;
+ }
+
+ std::string key_type () const {
+ return _key_type;
+ }
+
+ std::string key_id () const {
+ return _key_id;
+ }
+
+ std::string not_valid_before () const {
+ return _not_valid_before;
+ }
+
+ std::string not_valid_after () const {
+ return _not_valid_after;
+ }
+
+ std::string key_data () const {
+ return _key_data;
+ }
+
+private:
+ std::string get (unsigned char const **, int) const;
+ std::string get_uuid (unsigned char const **, int) const;
+ std::string get_hex (unsigned char const **, int) const;
+
+ std::string _structure_id;
+ std::string _signer_thumbprint;
+ std::string _cpl_id;
+ std::string _key_type;
+ std::string _key_id;
+ std::string _not_valid_before;
+ std::string _not_valid_after;
+ std::string _key_data;
+};
+
+class KDM
+{
+public:
+ KDM (boost::filesystem::path, boost::filesystem::path);
+
+ std::list<KDMCipher> ciphers () const {
+ return _ciphers;
+ }
+
+private:
+ std::list<KDMCipher> _ciphers;
+};
+
+
+}
+
+
diff --git a/src/wscript b/src/wscript
index 93d6d5c1..acbfdfc0 100644
--- a/src/wscript
+++ b/src/wscript
@@ -10,19 +10,20 @@ def build(bld):
obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1'
obj.use = 'libkumu-libdcp libasdcp-libdcp'
obj.source = """
+ argb_frame.cc
asset.cc
- dcp.cc
certificates.cc
crypt_chain.cc
cpl.cc
+ dcp.cc
dcp_time.cc
gamma_lut.cc
+ kdm.cc
metadata.cc
mxf_asset.cc
picture_asset.cc
picture_frame.cc
reel.cc
- argb_frame.cc
sound_asset.cc
sound_frame.cc
subtitle_asset.cc
diff --git a/test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml b/test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml
new file mode 100644
index 00000000..79949013
--- /dev/null
+++ b/test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DCinemaSecurityMessage xmlns="http://www.smpte-ra.org/schemas/430-3/2006/ETM" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:enc="http://www.w3.org/2001/04/xmlenc#">
+ <!-- cinemaslides v0.2013.04.27 smpte kdm t1 -->
+ <AuthenticatedPublic Id="ID_AuthenticatedPublic">
+ <MessageId>urn:uuid:8971c838-d0c3-405d-bc57-43afa9d91242</MessageId>
+ <MessageType>http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type</MessageType>
+ <AnnotationText>cinemaslides 2013-07-06T21:10:29+01:00</AnnotationText>
+ <IssueDate>2013-07-06T21:10:29+01:00</IssueDate>
+ <Signer>
+ <ds:X509IssuerName>dnQualifier=5\+kdb\+9R\+/FhxzwAJ71I2V2cxEM=,CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</ds:X509IssuerName>
+ <ds:X509SerialNumber>7</ds:X509SerialNumber>
+ </Signer>
+ <RequiredExtensions>
+ <KDMRequiredExtensions xmlns="http://www.smpte-ra.org/schemas/430-1/2006/KDM">
+ <Recipient>
+ <X509IssuerSerial>
+ <ds:X509IssuerName>dnQualifier=L7hRU1k0AaJM23TJg2PYWmflEVk=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</ds:X509IssuerName>
+ <ds:X509SerialNumber>5</ds:X509SerialNumber>
+ </X509IssuerSerial>
+ <X509SubjectName>dnQualifier=L7hRU1k0AaJM23TJg2PYWmflEVk=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</X509SubjectName>
+ </Recipient>
+ <CompositionPlaylistId>urn:uuid:eece17de-77e8-4a55-9347-b6bab5724b9f</CompositionPlaylistId>
+ <ContentTitleText>TONEPLATES-SMPTE-ENCRYPTED_TST_F_XX-XX_ITL-TD_51-XX_2K_WOE_20111001_WOE_OV</ContentTitleText>
+ <ContentKeysNotValidBefore>2013-07-06T20:04:58+00:00</ContentKeysNotValidBefore>
+ <ContentKeysNotValidAfter>2023-07-02T20:04:56+00:00</ContentKeysNotValidAfter>
+ <AuthorizedDeviceInfo>
+ <DeviceListIdentifier>urn:uuid:014d91e5-616d-4c21-8cfa-48da04b5b9d8</DeviceListIdentifier>
+ <DeviceListDescription>.smpte-430-2.ROOT.NOT_FOR_PRODUCTION</DeviceListDescription>
+ <DeviceList>
+ <CertificateThumbprint>/Pk3/NK4Quql1hKTLnASAi5xzL0=</CertificateThumbprint>
+ </DeviceList>
+ </AuthorizedDeviceInfo>
+ <KeyIdList>
+ <TypedKeyId>
+ <KeyType>MDIK</KeyType>
+ <KeyId>urn:uuid:4ac4f922-8239-4831-b23b-31426d0542c4</KeyId>
+ </TypedKeyId>
+ <TypedKeyId>
+ <KeyType>MDAK</KeyType>
+ <KeyId>urn:uuid:73baf5de-e195-4542-ab28-8a465f7d4079</KeyId>
+ </TypedKeyId>
+ </KeyIdList>
+ <ForensicMarkFlagList>
+ <ForensicMarkFlag>http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable</ForensicMarkFlag>
+ <ForensicMarkFlag>http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable</ForensicMarkFlag>
+ </ForensicMarkFlagList>
+ </KDMRequiredExtensions>
+ </RequiredExtensions>
+ <NonCriticalExtensions/>
+ </AuthenticatedPublic>
+ <AuthenticatedPrivate Id="ID_AuthenticatedPrivate">
+ <enc:EncryptedKey>
+ <enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
+ <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+ </enc:EncryptionMethod>
+ <enc:CipherData>
+ <enc:CipherValue>BGMEgy18q3BwOD6jMrgxlwjI/FKUVTgcRe1gpTUxg52HZr9Iu3iQo+AhO6hg4ti1
+1maakj3gi6b++8qKbm5HkqA8hzjsh54NWf/cOGnHHsM/o+1fxLqPI5u37EHymxv0
+uC18F2Ad9g1wUf0BITDrtdjC19yTGoPYD+VHuUZvZrIHs5Otw4Buhg1+N3VDc0vz
+/n+udK9MkktRY+Z0LPyDARcXksjFPkTKkZBOw815StG7GXlGcg0ndMVVN7+9l4Xf
+97EWRTv3wEoMI140NA5vvcPWm8DjbaZgShlmOf9ZqM6N24eTRKut+62ljsupMGFB
+ffYMzb6hsGwaYKVzPw2W3A==</enc:CipherValue>
+ </enc:CipherData>
+ </enc:EncryptedKey>
+ <enc:EncryptedKey>
+ <enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
+ <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+ </enc:EncryptionMethod>
+ <enc:CipherData>
+ <enc:CipherValue>PAWbXs8QI1+opF9jrcuwqcM1QIl6/pB4zlzvBMpmidiwgqozox1DIMgTt5K9wqV5
+hYiLfEgyiXrJcO9IbUa1g7YeySP6In/FGu/JcdYe/dAe0II7F8xJhIxZHRT0l5PE
+ZmiLFZO3Ax5aluKX2PzIztrxdrIlGI3jAMLVACYiNQGAlwUCobhOFwmyXX/x+0iN
+ULJ459WCsY45AMkSAWzoztR8xY8DNa9K6zTOotxFemv+wjwFqSdzyHhaZOWvYHan
+0JtPuELBZnuMcqvMUsQ5TETYak2Pj2QCVWX2Ahcek73oyRWWK7gwKf5DRTMoOwtP
+gKhBADcETNFQligfz33wMA==</enc:CipherValue>
+ </enc:CipherData>
+ </enc:EncryptedKey>
+ </AuthenticatedPrivate>
+ <ds:Signature>
+ <ds:SignedInfo>
+ <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+ <ds:Reference URI="#ID_AuthenticatedPublic">
+ <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+ <ds:DigestValue>u/EsQFMekv8vXKn6glbc1vvNZptz18/UKkVBtdrgFOU=</ds:DigestValue>
+ </ds:Reference>
+ <ds:Reference URI="#ID_AuthenticatedPrivate">
+ <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+ <ds:DigestValue>OaEm1HINvc6t9a/Z6ut1+C3JV+sOPDeRGv13TWVXsO8=</ds:DigestValue>
+ </ds:Reference>
+ </ds:SignedInfo>
+ <ds:SignatureValue>TMeBgzdDJxn4aNCJnaaXd91m4B91qmstqluYEC9e0TkX6YWeEmqeOCwcjy7aXk4P
+f66zGtvNsEFA9b0d6zpqX7XnlR7cgYS++jeIjgqzIHAoMQSu4GdjFo4WAutoHkab
+a6+LwzWbU2nPCcvZT0BSLxbfrczNEGwzlfY5IU0cUtCrI5KcdobI0jecgEhiZksR
+OzBP2CxWC2NfOZYsemSOxCzazjRpl7zY5c0Bm2yEzjVmoY2IQQIaoK7+MKyHxDxe
+JroVBnynlFLznjutTfQ7VIWiCUOkNuRstxlLb6xA7mijzMI8JaTjoLfHF7Hv/BDx
+cZg3PhW/dxLWw1v3u3RQmQ==</ds:SignatureValue>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509IssuerSerial>
+ <ds:X509IssuerName>dnQualifier=5\+kdb\+9R\+/FhxzwAJ71I2V2cxEM=,CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</ds:X509IssuerName>
+ <ds:X509SerialNumber>7</ds:X509SerialNumber>
+ </ds:X509IssuerSerial>
+ <ds:X509Certificate>MIIEezCCA2OgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBijEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMTUwMwYDVQQDFCwuc21wdGUt
+NDMwLTIuSU5URVJNRURJQVRFLk5PVF9GT1JfUFJPRFVDVElPTjElMCMGA1UELhMc
+NStrZGIrOVIrL0ZoeHp3QUo3MUkyVjJjeEVNPTAeFw0xMzA3MDYyMDA0NTdaFw0y
+MzA3MDIyMDA0NTdaMIGEMRQwEgYDVQQKEwtleGFtcGxlLm9yZzEUMBIGA1UECxML
+ZXhhbXBsZS5vcmcxLzAtBgNVBAMUJkNTLnNtcHRlLTQzMC0yLkxFQUYuTk9UX0ZP
+Ul9QUk9EVUNUSU9OMSUwIwYDVQQuExxWMWJVOWRoZlBEZFdZNldydzlXQjdJYWR4
+QWc9MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA22bL2XgFrlegWg1Y
+wA62mL4VOTAz7jY6s01JfdwxKIsYW+A7Tgg2LmasTKsCGiJnXcRHRN21s0I3dLV7
+kxtig9STnSFiIPdMbMk9f06xFvntshc8cYgEfPldA+hfkaiBkOxQ235kUpo/uN1o
+vL1xWr5PfdjJNz2/WcfEWeQddHy5mc4SFv/TvQsiaPuwr6iI/1vUEMIRkmiu6G7r
+Fs9kuL+KljF71eorwZagv+OFOd1+M7ejT+jlcWdqCMBBomcHvzduUgP8VpKHc89E
+hDxDQsFLqmmWaNenra4XHOFVrYcs+aGKjPCSaFTggTNOOMFI6qVXW/ZsE03zmgS/
+aNEq1wIDAQABo4HvMIHsMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgWgMB0GA1Ud
+DgQWBBRXVtT12F88N1ZjpavD1YHshp3ECDCBrwYDVR0jBIGnMIGkgBTn6R1v71H7
+8WHHPAAnvUjZXZzEQ6GBiKSBhTCBgjEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDAS
+BgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUtNDMwLTIuUk9PVC5O
+T1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THEw3aFJVMWswQWFKTTIzVEpnMlBZ
+V21mbEVWaz2CAQYwDQYJKoZIhvcNAQELBQADggEBAHV2HYFW4PEWOYZq+Y6KnFcD
+bod8NCsCmTmO+L7zXi3yY4rtqqP3aKbr4nGINaC8+XqY6fYsgaNvPRInn+Nkh7xH
+FQbi8RUuFQ4R75O8VhXk3JYhwDoES/FPOqwtHN3MiOO15dbWwLkwqa/K2bkkZbpG
+5mFmuHHy8GomvZJkZQ6TNvlMQWj4+FgrBM0EFuJwm8LS+Ek7f98Gi4yxlBumMt4X
+N/s/8ejaChGueEGX9Akiu5KFBZrpEzneveLXHndaL56slmyTe38R07EbXwHK35Rk
+giGcueg9LxGmWfB7hS4ONx2R4oWYN4Naf9QRD3u37dpv8w3mHj2SihUPmCMxL1E=</ds:X509Certificate>
+ </ds:X509Data>
+ <ds:X509Data>
+ <ds:X509IssuerSerial>
+ <ds:X509IssuerName>dnQualifier=L7hRU1k0AaJM23TJg2PYWmflEVk=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</ds:X509IssuerName>
+ <ds:X509SerialNumber>6</ds:X509SerialNumber>
+ </ds:X509IssuerSerial>
+ <ds:X509Certificate>MIIEfzCCA2egAwIBAgIBBjANBgkqhkiG9w0BAQsFADCBgjEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUt
+NDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THEw3aFJVMWsw
+QWFKTTIzVEpnMlBZV21mbEVWaz0wHhcNMTMwNzA2MjAwNDU2WhcNMjMwNzAzMjAw
+NDU2WjCBijEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUu
+b3JnMTUwMwYDVQQDFCwuc21wdGUtNDMwLTIuSU5URVJNRURJQVRFLk5PVF9GT1Jf
+UFJPRFVDVElPTjElMCMGA1UELhMcNStrZGIrOVIrL0ZoeHp3QUo3MUkyVjJjeEVN
+PTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPhsUkgBjWY4yTaegpvy
+cW/S3gVcBZN+GO1qBxMZADAlp87YKVGw5TRfHjIppC0wClq3zS5rSv07diAOcZd7
+pXqusCokrTHBsKG3S8u0JEXn2AWnNgX7dD8gxE1ZV41Mm+reI4eE9fpntgzqrV6E
+x8qlxkCAHrBPJNWVJHfpVifeKtjbb4/4K6GK9pZ/ejZ4tDBrYgS15S6s/7IZOyVb
+d/XckanEcCeNEv7KgW8nwbm5ryI27++ogJECCBzadGHcb/lcx/xSVqAlMuBK884c
+duz16+u+OC+1TLCb11B5VVWoVji5CGW1sOYVaMK+XZeyyIjk+fhF0P8DU9SOSa5f
+cZMCAwEAAaOB9TCB8jASBgNVHRMBAf8ECDAGAQH/AgECMAsGA1UdDwQEAwIBBjAd
+BgNVHQ4EFgQU5+kdb+9R+/FhxzwAJ71I2V2cxEMwga8GA1UdIwSBpzCBpIAUL7hR
+U1k0AaJM23TJg2PYWmflEVmhgYikgYUwgYIxFDASBgNVBAoTC2V4YW1wbGUub3Jn
+MRQwEgYDVQQLEwtleGFtcGxlLm9yZzEtMCsGA1UEAxQkLnNtcHRlLTQzMC0yLlJP
+T1QuTk9UX0ZPUl9QUk9EVUNUSU9OMSUwIwYDVQQuExxMN2hSVTFrMEFhSk0yM1RK
+ZzJQWVdtZmxFVms9ggEFMA0GCSqGSIb3DQEBCwUAA4IBAQAHH9Wi9740Eh+BnpXf
+YztVm2x0Pzg9Xgr1O9iSNffQPf3C0GYA3WNFoWOnVfAUNpK8GU5vbFr8+CtdRPuW
+02dJJ3q0C0pi/21mxuYvQVX8vNFGbFK7C4AT7NdKgeMpFvPfGDapITd4iR/oQWw3
+lix+gb/CK0km5IWxNJN2YkzM3YUf9SHBr05gJqtBciXa0ZhIMqR4qXNokzvKotuH
+ZZZz9GsnHur4SmflEffbc5xZsMWFZWq5w0uSIvE4s9ZrugC1HKQWlS+1At8E0att
+33S8pGKbOdhSK62c/wXapxAouVzx0M8izJ2GEGh7BPpzAP4k5d3LYxw8MHXlem0H
+BGSw</ds:X509Certificate>
+ </ds:X509Data>
+ <ds:X509Data>
+ <ds:X509IssuerSerial>
+ <ds:X509IssuerName>dnQualifier=L7hRU1k0AaJM23TJg2PYWmflEVk=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org</ds:X509IssuerName>
+ <ds:X509SerialNumber>5</ds:X509SerialNumber>
+ </ds:X509IssuerSerial>
+ <ds:X509Certificate>MIIEdzCCA1+gAwIBAgIBBTANBgkqhkiG9w0BAQsFADCBgjEUMBIGA1UEChMLZXhh
+bXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUub3JnMS0wKwYDVQQDFCQuc21wdGUt
+NDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJT04xJTAjBgNVBC4THEw3aFJVMWsw
+QWFKTTIzVEpnMlBZV21mbEVWaz0wHhcNMTMwNzA2MjAwNDU2WhcNMjMwNzA0MjAw
+NDU2WjCBgjEUMBIGA1UEChMLZXhhbXBsZS5vcmcxFDASBgNVBAsTC2V4YW1wbGUu
+b3JnMS0wKwYDVQQDFCQuc21wdGUtNDMwLTIuUk9PVC5OT1RfRk9SX1BST0RVQ1RJ
+T04xJTAjBgNVBC4THEw3aFJVMWswQWFKTTIzVEpnMlBZV21mbEVWaz0wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQn+d3YhStTcHlzYToU6z7NE/YF+G
+JJalU1BHa58aQ9272o4p/c0+2KJOvl5peIYASZYpuGQpv802H4cP6axCt44a6MJx
+j9P63nq83TKdeus85Lr3ACk22646rlDh2PpxOxWrzvsvGE0PCI1Pa7kPFg9xtvi2
+zLE3wPIHtZ326VEv8BIwWXKPwEYp/HOUQvTZf2pWtZTXHQZ9T8k8Vtq32e3tyEsf
+icC4p1arYAT1AWWVaJonTscLoz8HledVIx3yv+QN0AJ3wf5QcJRx5t2Tdc7O9ovl
+Uzrl9fzhAfzPUtmIG1aCoZVZCQgaKy09+QFN6jZxWT89DuSBygZTEHKRAgMBAAGj
+gfUwgfIwEgYDVR0TAQH/BAgwBgEB/wIBAzALBgNVHQ8EBAMCAQYwHQYDVR0OBBYE
+FC+4UVNZNAGiTNt0yYNj2Fpn5RFZMIGvBgNVHSMEgacwgaSAFC+4UVNZNAGiTNt0
+yYNj2Fpn5RFZoYGIpIGFMIGCMRQwEgYDVQQKEwtleGFtcGxlLm9yZzEUMBIGA1UE
+CxMLZXhhbXBsZS5vcmcxLTArBgNVBAMUJC5zbXB0ZS00MzAtMi5ST09ULk5PVF9G
+T1JfUFJPRFVDVElPTjElMCMGA1UELhMcTDdoUlUxazBBYUpNMjNUSmcyUFlXbWZs
+RVZrPYIBBTANBgkqhkiG9w0BAQsFAAOCAQEAQYn1ySQE/cvAfy4g7m0w03mPKhbf
+rNI56HrZJebrJGvBItWafzVTlTiHY6AgepGQ/qdfXIK4EttSRIeXTDxlLA9Mf+mw
+kU9E6ZR4WBVGD+zLw5z/aFakNTyYDk0o/gxe3njNY6/KT+0fdmtnfnAVymqDE3z7
+Nm6UfDMVsrJ/wIZ7TKGmeo3IwAcPeF5+SminJ3OhpRDSeY+ZomwEtPvpi3N1tlHk
+vYZE7iB+onjFT+FOjtLCeGtRbePfJuh8YrSGzc6dCeCeK0CwUr3bvnVfqzhiZYRn
+6KGFx4EGJ//GOagj4VOKtftV0+SZeQtwBxhuzkBnx+wH48uuhNa7rwtslQ==</ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </ds:Signature>
+</DCinemaSecurityMessage>
diff --git a/test/data/private.key b/test/data/private.key
new file mode 100644
index 00000000..09991ac5
--- /dev/null
+++ b/test/data/private.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA00J/nd2IUrU3B5c2E6FOs+zRP2BfhiSWpVNQR2ufGkPdu9qO
+Kf3NPtiiTr5eaXiGAEmWKbhkKb/NNh+HD+msQreOGujCcY/T+t56vN0ynXrrPOS6
+9wApNtuuOq5Q4dj6cTsVq877LxhNDwiNT2u5DxYPcbb4tsyxN8DyB7Wd9ulRL/AS
+MFlyj8BGKfxzlEL02X9qVrWU1x0GfU/JPFbat9nt7chLH4nAuKdWq2AE9QFllWia
+J07HC6M/B5XnVSMd8r/kDdACd8H+UHCUcebdk3XOzvaL5VM65fX84QH8z1LZiBtW
+gqGVWQkIGistPfkBTeo2cVk/PQ7kgcoGUxBykQIDAQABAoIBAQDEONfgAYxFhhv+
+6OxJf2JirGwOua0AI6vpE2FVdRjqhG7HtVCmoB6raPwYt6lpvZB5Y+AeEC64uHYa
+912z4g8mSOetBtTUNgtm0I6gLbR9oVYt0SX3liax1YoW5yIpJCg6U+7lDfMw4xlw
+BTl7s0rIQG1H6fNJlwZkCsrDbmWyl4PKcb6Kqfw4iafQmCRAJx9gvYW0QXigaxU5
+xSJRiA5Y4Ts0ZhH+RwKzNVap4IhWtKoIZS2Vvz+H1/iPavs9axdcyhfXg+9mOkT1
+4x3LQlNAmcYzUBByJN1d+yzoCVb3s8aP5vcWvR3t9TlnutBhKFGvJo0Jbfx1z5fx
+b45+rjihAoGBAOv3thZeyqfLjl3tG00EhkDDxVvlDYtCfuyhNT7w/tCWlyoS1Wsh
+iar0/yp/7wXMkasEaq0E/G2Srez39F9wd2GSuymmtMBzzXL8SXWIpfYlizgo/Zun
+fRNNMDxYsY4Gaq4/QU7YCOzmAs1iZPyk10s8RYkUuS49ZGbgC1fy1bUDAoGBAOUx
+z4/4R71mIlyr5/XGoWt7HDy7LD1AFlscBJ7bTFTzePuL8kawrm0P5Rg4zTTkJqZs
+/ukVng+PRD+gY0cPEdwrZH6CTxMjxps1TPSGrM7KFK6//J+ez5iYordVhMEB2vRC
+tIVN2tEcx9JdqCUb1k1esBlCZ1E1tyaFsAMzZTPbAoGBAJ+RbYMHKwf5MRb3JkUY
+0CivuqB/7n7Dws0F3tnnYolvrF15SvUdQtlmv77fsKU9Ryxc2j6SZpk6XX8n1gtM
+JI7JCOQLpaOXK9GcJZjjhUdE8DZUEdvWkVAiHIJSgA9I649VmzZUBQUeLbrx1NS8
+LBLmeSdP6kIIpgKjc0hQIJA9AoGAQAejaiA9wo73CJbKDeK5E/Ln15ue51mxJTD1
+pX+0moMOiI/3VoJjqHppUVQFGEKo/ZOzv2BXsRcgRPpE4gQ2xCCnSaST/M/g21mP
+rzy0qGzSTGO8aseOTZ5OzxMoLFedWOIN1rQCbqsws/eQUxWs0B6k6dmgTZLJIQz2
+OF6yZkMCgYArWYrcsefBFznwckK3S+EXZeYApyREmWywHtuC6NKmWfksRtDce8B6
+Hs6T7xClvbd99h4CNf4nywRBRAO85HfSTkjamLB42xoGrWizeN3XGou6NA5im9IO
+dRSNHaS084mEm9/HaxfvWHTLsUaArjDG/26ji236Dr6m3+ghDIUJMQ==
+-----END RSA PRIVATE KEY-----
diff --git a/test/kdm_test.cc b/test/kdm_test.cc
new file mode 100644
index 00000000..172661e3
--- /dev/null
+++ b/test/kdm_test.cc
@@ -0,0 +1,44 @@
+/*
+ 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 "kdm.h"
+
+BOOST_AUTO_TEST_CASE (kdm_test)
+{
+ libdcp::KDM kdm (
+ "test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml",
+ "test/data/private.key"
+ );
+
+ list<libdcp::KDMCipher> ciphers = kdm.ciphers ();
+
+ BOOST_CHECK_EQUAL (ciphers.size(), 2);
+
+ BOOST_CHECK_EQUAL (ciphers.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
+ BOOST_CHECK_EQUAL (ciphers.front().key_id(), "4ac4f922-8239-4831-b23b-31426d0542c4");
+ BOOST_CHECK_EQUAL (ciphers.front().not_valid_before(), "2013-07-06T20:04:58+00:00");
+ BOOST_CHECK_EQUAL (ciphers.front().not_valid_after(), "2023-07-02T20:04:56+00:00");
+ BOOST_CHECK_EQUAL (ciphers.front().key_data(), "8a2729c3e5b65c45d78305462104c3fb");
+
+ BOOST_CHECK_EQUAL (ciphers.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
+ BOOST_CHECK_EQUAL (ciphers.back().key_id(), "73baf5de-e195-4542-ab28-8a465f7d4079");
+ BOOST_CHECK_EQUAL (ciphers.back().not_valid_before(), "2013-07-06T20:04:58+00:00");
+ BOOST_CHECK_EQUAL (ciphers.back().not_valid_after(), "2023-07-02T20:04:56+00:00");
+ BOOST_CHECK_EQUAL (ciphers.back().key_data(), "5327fb7ec2e807bd57059615bf8a169d");
+}
diff --git a/test/tests.cc b/test/tests.cc
index 4fc81571..49c6689f 100644
--- a/test/tests.cc
+++ b/test/tests.cc
@@ -70,6 +70,7 @@ wav (libdcp::Channel)
static string test_corpus = "../libdcp-test";
+#include "kdm_test.cc"
#include "decryption_test.cc"
#include "dcp_test.cc"
#include "error_test.cc"