Hello.
+== Acknowledgements
+
+Wolfgang Woehl's cinemaslides was most informative on the
+nasty details of encryption.
+
+
+
+
== Building:
./waf configure
--- /dev/null
+#include <sstream>
+#include <vector>
+#include <boost/algorithm/string.hpp>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/asn1.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 ()
+{
+ X509_free (_certificate);
+}
+
+string
+Certificate::issuer () const
+{
+ X509_NAME* n = X509_get_issuer_name (_certificate);
+ assert (n);
+
+ char b[256];
+ X509_NAME_oneline (n, b, 256);
+ return b;
+}
+
+string
+Certificate::name_for_xml (string const & n)
+{
+ stringstream x;
+
+ vector<string> p;
+ boost::split (p, n, boost::is_any_of ("/"));
+ for (vector<string>::const_reverse_iterator i = p.rbegin(); i != p.rend(); ++i) {
+ x << *i << ",";
+ }
+
+ return x.str().substr(0, x.str().length() - 2);
+}
+
+string
+Certificate::subject () const
+{
+ X509_NAME* n = X509_get_subject_name (_certificate);
+ assert (n);
+
+ char b[256];
+ X509_NAME_oneline (n, b, 256);
+ return b;
+}
+
+string
+Certificate::serial () const
+{
+ ASN1_INTEGER* s = X509_get_serialNumber (_certificate);
+ assert (s);
+
+ BIGNUM* b = ASN1_INTEGER_to_BN (s, 0);
+ char* c = BN_bn2dec (b);
+ BN_free (b);
+
+ string st (c);
+ OPENSSL_free (c);
+
+ return st;
+}
+
+
+/** @param filename Text file of PEM-format certificates,
+ * in the order:
+ *
+ * 1. self-signed root certificate
+ * 2. intermediate certificate signed by root certificate
+ * ...
+ * n. leaf certificate signed by previous intermediate.
+ */
+
+CertificateChain::CertificateChain (string const & filename)
+{
+ FILE* f = fopen (filename.c_str(), "r");
+ if (!f) {
+ throw FileError ("could not open file", filename);
+ }
+
+ while (1) {
+ X509* c = 0;
+ if (!PEM_read_X509 (f, &c, 0, 0)) {
+ break;
+ }
+
+ _certificates.push_back (shared_ptr<Certificate> (new Certificate (c)));
+ }
+}
+
+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 ();
+}
--- /dev/null
+#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 libdcp {
+
+class Certificate : public boost::noncopyable
+{
+public:
+ Certificate (X509 *);
+ ~Certificate ();
+
+ std::string issuer () const;
+ std::string serial () const;
+ std::string subject () const;
+
+ static std::string name_for_xml (std::string const &);
+
+private:
+ X509* _certificate;
+};
+
+class CertificateChain
+{
+public:
+ CertificateChain () {}
+ CertificateChain (std::string const &);
+
+ boost::shared_ptr<Certificate> root () const;
+ boost::shared_ptr<Certificate> leaf () const;
+
+private:
+ friend class ::certificates;
+ std::list<boost::shared_ptr<Certificate> > _certificates;
+};
+
+}
+
+#endif
DCP::DCP (string directory)
: _directory (directory)
+ , _encrypted (false)
{
boost::filesystem::create_directories (directory);
}
DCP::write_xml () const
{
for (list<shared_ptr<const CPL> >::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
- (*i)->write_xml ();
+ (*i)->write_xml (_encrypted, _certificates);
}
string pkl_uuid = make_uuid ();
}
void
-CPL::write_xml () const
+CPL::write_xml (bool encrypted, CertificateChain const & certificates) const
{
boost::filesystem::path p;
p /= _directory;
os << " </AssetList>\n"
<< " </Reel>\n"
- << " </ReelList>\n"
- << "</CompositionPlaylist>\n";
+ << " </ReelList>\n";
+
+ if (encrypted) {
+ os << " <dsig:X509Data>\n"
+ << " <dsig:X509IssuerSerial>\n"
+ << " <dsig:X509IssuerName>" << Certificate::name_for_xml (certificates.leaf()->issuer()) << "</dsig:IssuerName>\n"
+ << " <dsig:X509SerialNumber>" << certificates.leaf()->serial() << "</dsig:X509SerialNumber>\n"
+ << " <dsig:X509IssuerSerial>\n"
+ << " <dsig:X509SubjectName>" << Certificate::name_for_xml (certificates.leaf()->subject()) << "</dsig:X509SubjectName>\n"
+ << " </dsig:X509Data>\n";
+ }
+
+ os << "</CompositionPlaylist>\n";
os.close ();
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include "types.h"
+#include "certificates.h"
namespace xmlpp {
class Node;
bool equals (CPL const & other, EqualityOptions options, std::list<std::string>& notes) const;
- void write_xml () const;
+ void write_xml (bool, CertificateChain const &) const;
void write_to_assetmap (std::ostream& s) const;
void write_to_pkl (std::ostream& s) const;
/** the directory that we are writing to */
std::string _directory;
std::list<boost::shared_ptr<const CPL> > _cpls;
+
+ bool _encrypted;
+ CertificateChain _certificates;
};
}
std::string to_string () const;
int64_t to_ticks () const;
+
+private:
+ void set (double);
};
extern bool operator== (Time const & a, Time const & b);
obj.source = """
asset.cc
asset_map.cc
+ certificates.cc
cpl_file.cc
dcp.cc
dcp_time.cc
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<VolumeIndex xmlns="http://www.smpte-ra.org/schemas/429-9/2007/AM">
+ <Index>1</Index>
+</VolumeIndex>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<CompositionPlaylist xmlns="http://www.smpte-ra.org/schemas/429-7/2006/CPL">
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</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:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00</Id>
+ <LabelText>81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00</LabelText>
+ </ContentVersion>
+ <RatingList/>
+ <ReelList>
+ <Reel>
+ <Id>urn:uuid:379fa64c-ad71-46cf-bef7-b45624006610</Id>
+ <AssetList>
+ <MainPicture>
+ <Id>urn:uuid:d36f4bb3-c4fa-4a95-9915-6fec3110cd71</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:c38bdd62-ce03-4988-8603-195f134207c7</Id>
+ <AnnotationText>audio.mxf</AnnotationText>
+ <EditRate>24 1</EditRate>
+ <IntrinsicDuration>24</IntrinsicDuration>
+ <EntryPoint>0</EntryPoint>
+ <Duration>24</Duration>
+ </MainSound>
+ </AssetList>
+ </Reel>
+ </ReelList>
+</CompositionPlaylist>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<AssetMap xmlns="http://www.smpte-ra.org/schemas/429-9/2007/AM">
+ <Id>urn:uuid:b135d5cf-d180-43d8-b0b3-7373737b73bf</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:df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb</Id>
+ <PackingList>true</PackingList>
+ <ChunkList>
+ <Chunk>
+ <Path>df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb_pkl.xml</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>1049</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>81fb54df-e1bf-4647-8788-ea7ba154375b_cpl.xml</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>1526</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:c38bdd62-ce03-4988-8603-195f134207c7</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>audio.mxf</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>305326</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:d36f4bb3-c4fa-4a95-9915-6fec3110cd71</Id>
+ <ChunkList>
+ <Chunk>
+ <Path>video.mxf</Path>
+ <VolumeIndex>1</VolumeIndex>
+ <Offset>0</Offset>
+ <Length>26080</Length>
+ </Chunk>
+ </ChunkList>
+ </Asset>
+ </AssetList>
+</AssetMap>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<VolumeIndex xmlns="http://www.smpte-ra.org/schemas/429-9/2007/AM">
+ <Index>1</Index>
+</VolumeIndex>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<PackingList xmlns="http://www.smpte-ra.org/schemas/429-8/2007/PKL">
+ <Id>urn:uuid:df0e4141-13c3-4a7a-bef8-b5a04fcbc4bb</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:c38bdd62-ce03-4988-8603-195f134207c7</Id>
+ <AnnotationText>audio.mxf</AnnotationText>
+ <Hash>+qImGHkt/XouNaJ1V/+7BtcB4VU=</Hash>
+ <Size>305326</Size>
+ <Type>application/mxf</Type>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:d36f4bb3-c4fa-4a95-9915-6fec3110cd71</Id>
+ <AnnotationText>video.mxf</AnnotationText>
+ <Hash>E2vhyxdJQhEzSQZdp31w84ZZpfk=</Hash>
+ <Size>26080</Size>
+ <Type>application/mxf</Type>
+ </Asset>
+ <Asset>
+ <Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b</Id>
+ <Hash>TTn7vvdUQi/G+KaW1Pym/DjxULM=</Hash>
+ <Size>1526</Size>
+ <Type>text/xml</Type>
+ </Asset>
+ </AssetList>
+</PackingList>
#include "picture_asset.h"
#include "sound_asset.h"
#include "reel.h"
+#include "certificates.h"
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE libdcp_test
#include <boost/test/unit_test.hpp>
using std::string;
+using std::cout;
using std::vector;
using std::list;
using boost::shared_ptr;
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 ();
24,
32,
32,
- false
+ true
));
shared_ptr<libdcp::SoundAsset> ms (new libdcp::SoundAsset (
24,
24,
2,
- false
+ true
));
cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (mp, ms, shared_ptr<libdcp::SubtitleAsset> ())));
d.write_xml ();
}
+
+BOOST_AUTO_TEST_CASE (certificates)
+{
+ libdcp::CertificateChain c ("test/data/certificate_chain");
+ BOOST_CHECK_EQUAL (c._certificates.size(), 3);
+
+ 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"
+ );
+}