Quite large reworking of signer/cert handling.
authorCarl Hetherington <cth@carlh.net>
Thu, 17 Jul 2014 23:15:34 +0000 (00:15 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 17 Jul 2014 23:15:34 +0000 (00:15 +0100)
16 files changed:
src/certificate_chain.cc [new file with mode: 0644]
src/certificate_chain.h [new file with mode: 0644]
src/certificates.cc
src/certificates.h
src/decrypted_kdm.cc
src/decrypted_kdm.h
src/signer.cc
src/signer.h
src/signer_chain.cc [deleted file]
src/signer_chain.h [deleted file]
src/wscript
test/certificates_test.cc
test/decryption_test.cc
test/encryption_test.cc
test/kdm_test.cc
test/round_trip_test.cc

diff --git a/src/certificate_chain.cc b/src/certificate_chain.cc
new file mode 100644 (file)
index 0000000..dbed590
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+    Copyright (C) 2013-2014 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.
+
+*/
+
+/** @file  src/signer_chain.cc
+ *  @brief Functions to make signer chains.
+ */
+
+#include "certificate_chain.h"
+#include "exceptions.h"
+#include "util.h"
+#include "KM_util.h"
+#include <openssl/sha.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <sstream>
+
+using std::string;
+using std::ofstream;
+using std::ifstream;
+using std::stringstream;
+using std::cout;
+
+/** Run a shell command.
+ *  @param cmd Command to run (UTF8-encoded).
+ */
+static void
+command (string cmd)
+{
+#ifdef LIBDCP_WINDOWS
+       /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
+          is handled correctly.
+       */
+       int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
+       wchar_t* buffer = new wchar_t[wn];
+       if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
+               delete[] buffer;
+               return;
+       }
+
+       int code = 1;
+
+       STARTUPINFOW startup_info;
+       memset (&startup_info, 0, sizeof (startup_info));
+       startup_info.cb = sizeof (startup_info);
+       PROCESS_INFORMATION process_info;
+
+       /* XXX: this doesn't actually seem to work; failing commands end up with
+          a return code of 0
+       */
+       if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
+               WaitForSingleObject (process_info.hProcess, INFINITE);
+               DWORD c;
+               if (GetExitCodeProcess (process_info.hProcess, &c)) {
+                       code = c;
+               }
+               CloseHandle (process_info.hProcess);
+               CloseHandle (process_info.hThread);
+       }
+
+       delete[] buffer;
+#else
+       cmd += " 2> /dev/null";
+       int const r = system (cmd.c_str ());
+       int const code = WEXITSTATUS (r);
+#endif
+       if (code) {
+               stringstream s;
+               s << "error " << code << " in " << cmd << " within " << boost::filesystem::current_path();
+               throw dcp::MiscError (s.str());
+       }
+}
+
+/** Extract a public key from a private key and create a SHA1 digest of it.
+ *  @param private_key Private key
+ *  @param openssl openssl binary name (or full path if openssl is not on the system path).
+ *  @return SHA1 digest of corresponding public key, with escaped / characters.
+ */
+static string
+public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
+{
+       boost::filesystem::path public_name = private_key.string() + ".public";
+
+       /* Create the public key from the private key */
+       stringstream s;
+       s << "\"" << openssl.string() << "\" rsa -outform PEM -pubout -in " << private_key.string() << " -out " << public_name.string ();
+       command (s.str().c_str ());
+
+       /* Read in the public key from the file */
+
+       string pub;
+       ifstream f (public_name.string().c_str ());
+       if (!f.good ()) {
+               throw dcp::MiscError ("public key not found");
+       }
+
+       bool read = false;
+       while (f.good ()) {
+               string line;
+               getline (f, line);
+               if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
+                       read = true;
+               } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
+                       break;
+               } else if (read) {
+                       pub += line;
+               }
+       }
+
+       /* Decode the base64 of the public key */
+               
+       unsigned char buffer[512];
+       int const N = dcp::base64_decode (pub, buffer, 1024);
+
+       /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
+
+       SHA_CTX context;
+       if (!SHA1_Init (&context)) {
+               throw dcp::MiscError ("could not init SHA1 context");
+       }
+
+       if (!SHA1_Update (&context, buffer + 24, N - 24)) {
+               throw dcp::MiscError ("could not update SHA1 digest");
+       }
+
+       unsigned char digest[SHA_DIGEST_LENGTH];
+       if (!SHA1_Final (digest, &context)) {
+               throw dcp::MiscError ("could not finish SHA1 digest");
+       }
+
+       char digest_base64[64];
+       string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
+#ifdef LIBDCP_WINDOWS
+       boost::replace_all (dig, "/", "\\/");
+#else  
+       boost::replace_all (dig, "/", "\\\\/");
+#endif 
+       return dig;
+}
+
+boost::filesystem::path
+dcp::make_certificate_chain (boost::filesystem::path openssl)
+{
+       boost::filesystem::path directory = boost::filesystem::unique_path ();
+       boost::filesystem::create_directories (directory);
+       
+       boost::filesystem::path const cwd = boost::filesystem::current_path ();
+       boost::filesystem::current_path (directory);
+
+       string quoted_openssl = "\"" + openssl.string() + "\"";
+
+       command (quoted_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";
+       }
+
+       string const ca_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION/dnQualifier=" + public_key_digest ("ca.key", openssl);
+
+       {
+               stringstream c;
+               c << quoted_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 (quoted_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";
+       }
+               
+       string const inter_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION/dnQualifier="
+               + public_key_digest ("intermediate.key", openssl);
+
+       {
+               stringstream s;
+               s << quoted_openssl
+                 << " req -new -config intermediate.cnf -days 3649 -subj " << inter_subject << " -key intermediate.key -out intermediate.csr";
+               command (s.str().c_str());
+       }
+
+       
+       command (
+               quoted_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 (quoted_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";
+       }
+
+       string const leaf_subject = "/O=example.org/OU=example.org/CN=CS.smpte-430-2.LEAF.NOT_FOR_PRODUCTION/dnQualifier="
+               + public_key_digest ("leaf.key", openssl);
+
+       {
+               stringstream s;
+               s << quoted_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 (
+               quoted_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"
+               );
+
+       boost::filesystem::current_path (cwd);
+
+       return directory;
+}
diff --git a/src/certificate_chain.h b/src/certificate_chain.h
new file mode 100644 (file)
index 0000000..6a6fc48
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+    Copyright (C) 2013-2014 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.
+
+*/
+
+/** @file  src/signer_chain.h
+ *  @brief Functions to make signer chains.
+ */
+
+#include <boost/filesystem.hpp>
+
+namespace dcp {
+
+/** Create a chain of certificates for signing things.
+ *  @param openssl Name of openssl binary (if it is on the path) or full path.
+ *  @return Directory (which should be deleted by the caller) containing:
+ *    - ca.self-signed.pem      self-signed root certificate
+ *    - intermediate.signed.pem intermediate certificate
+ *    - leaf.key                leaf certificate private key
+ *    - leaf.signed.pem         leaf certificate
+ */
+boost::filesystem::path make_certificate_chain (boost::filesystem::path openssl);
+
+}
index b331c6b6d00ab4fccd6257407e781661f82d56a9..46c60d6beb2e27477a774fec265142b16d288b0f 100644 (file)
@@ -294,7 +294,7 @@ Certificate::public_key () const
 }
 
 /** @return Root certificate */
-shared_ptr<Certificate>
+shared_ptr<const Certificate>
 CertificateChain::root () const
 {
        assert (!_certificates.empty());
@@ -302,7 +302,7 @@ CertificateChain::root () const
 }
 
 /** @return Leaf certificate */
-shared_ptr<Certificate>
+shared_ptr<const Certificate>
 CertificateChain::leaf () const
 {
        assert (_certificates.size() >= 2);
@@ -329,13 +329,13 @@ CertificateChain::leaf_to_root () const
  *  @param c Certificate to add.
  */
 void
-CertificateChain::add (shared_ptr<Certificate> c)
+CertificateChain::add (shared_ptr<const Certificate> c)
 {
        _certificates.push_back (c);
 }
 
 void
-CertificateChain::remove (shared_ptr<Certificate> c)
+CertificateChain::remove (shared_ptr<const Certificate> c)
 {
        _certificates.remove (c);
 }
@@ -357,11 +357,12 @@ CertificateChain::remove (int i)
        }
 }
 
-/** Verify the chain.
+/** Check to see if the chain is valid (i.e. root signs the intermediate, intermediate
+ *  signs the leaf and so on).
  *  @return true if it's ok, false if not.
  */
 bool
-CertificateChain::verify () const
+CertificateChain::valid () const
 {
        X509_STORE* store = X509_STORE_new ();
        if (!store) {
@@ -416,7 +417,7 @@ CertificateChain::attempt_reorder ()
        List original = _certificates;
        _certificates.sort ();
        do {
-               if (verify ()) {
+               if (valid ()) {
                        return true;
                }
        } while (std::next_permutation (_certificates.begin(), _certificates.end ()));
index 8ae562c91543e882d85c40163032b39b67a68812..06ce92a9dd76d8a7deb276a503492be25b6bbf99 100644 (file)
@@ -92,19 +92,19 @@ class CertificateChain
 public:
        CertificateChain () {}
 
-       void add (boost::shared_ptr<Certificate> c);
-       void remove (boost::shared_ptr<Certificate> c);
+       void add (boost::shared_ptr<const Certificate> c);
+       void remove (boost::shared_ptr<const Certificate> c);
        void remove (int);
 
-       boost::shared_ptr<Certificate> root () const;
-       boost::shared_ptr<Certificate> leaf () const;
+       boost::shared_ptr<const Certificate> root () const;
+       boost::shared_ptr<const Certificate> leaf () const;
 
-       typedef std::list<boost::shared_ptr<Certificate> > List;
+       typedef std::list<boost::shared_ptr<const Certificate> > List;
        
        List leaf_to_root () const;
        List root_to_leaf () const;
 
-       bool verify () const;
+       bool valid () const;
        bool attempt_reorder ();
 
 private:
index 8a714b1e05a086f44d6e1199300fd9c71e9c91a6..556cd9d54316e9f86d35f4490b54d441aeec2a39 100644 (file)
@@ -98,17 +98,16 @@ get (uint8_t ** p, int N)
        return g;
 }
 
-DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path private_key)
+DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, string private_key)
 {
        /* Read the private key */
-          
-       FILE* private_key_file = fopen_boost (private_key, "r");
-       if (!private_key_file) {
-               throw FileError ("could not find RSA private key file", private_key, errno);
+
+       BIO* bio = BIO_new_mem_buf (const_cast<char *> (private_key.c_str ()), -1);
+       if (!bio) {
+               throw MiscError ("could not create memory BIO");
        }
        
-       RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0);
-       fclose (private_key_file);      
+       RSA* rsa = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
        if (!rsa) {
                throw FileError ("could not read RSA private key file", private_key, errno);
        }
@@ -180,6 +179,7 @@ DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path pr
        }
 
        RSA_free (rsa);
+       BIO_free (bio);
 }
 
 DecryptedKDM::DecryptedKDM (
index 3c3d07db9e1a6e70db6534e973572b8fd49e5f74..dfba7563623d8402d30aa84f478fbd625d8fadb3 100644 (file)
@@ -48,9 +48,9 @@ class DecryptedKDM
 {
 public:
        /** @param kdm Encrypted KDM.
-        *  @param private_key Private key file name.
+        *  @param private_key Private key as a PEM-format string.
         */
-       DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path private_key);
+       DecryptedKDM (EncryptedKDM const & kdm, std::string private_key);
 
        /** Construct a DecryptedKDM.
         *  @param cpl CPL that the keys are for.
index a0d9912ad7974cef0c0a21a3e1a158a2bd38b0a0..55684759895a828eea1664ec441c281829671d53 100644 (file)
@@ -23,6 +23,8 @@
 
 #include "signer.h"
 #include "exceptions.h"
+#include "certificate_chain.h"
+#include "util.h"
 #include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
 #include <xmlsec/xmldsig.h>
@@ -37,6 +39,20 @@ using std::cout;
 using boost::shared_ptr;
 using namespace dcp;
 
+Signer::Signer (boost::filesystem::path openssl)
+{
+       boost::filesystem::path directory = make_certificate_chain (openssl);
+
+       _certificates.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (directory / "ca.self-signed.pem")));
+       _certificates.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (directory / "intermediate.signed.pem")));
+       _certificates.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (directory / "leaf.signed.pem")));
+
+       _key = dcp::file_to_string (directory / "leaf.key");
+
+       boost::filesystem::remove_all (directory);
+}
+       
+
 /** Add a &lt;Signer&gt; and &lt;ds:Signature&gt; nodes to an XML node.
  *  @param parent XML node to add to.
  *  @param standard INTEROP or SMPTE.
@@ -96,8 +112,8 @@ Signer::add_signature_value (xmlpp::Node* parent, string ns) const
        xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
 
        /* Add the certificate chain to the KeyInfo child node of parent */
-       list<shared_ptr<Certificate> > c = _certificates.leaf_to_root ();
-       for (list<shared_ptr<Certificate> >::iterator i = c.begin(); i != c.end(); ++i) {
+       CertificateChain::List c = _certificates.leaf_to_root ();
+       for (CertificateChain::List::iterator i = c.begin(); i != c.end(); ++i) {
                xmlpp::Element* data = key_info->add_child("X509Data", ns);
                
                {
@@ -134,3 +150,23 @@ Signer::add_signature_value (xmlpp::Node* parent, string ns) const
 
        xmlSecDSigCtxDestroy (signature_context);
 }
+
+bool
+Signer::valid () const
+{
+       if (!_certificates.valid ()) {
+               return false;
+       }
+
+       BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key.c_str ()), -1);
+       if (!bio) {
+               throw MiscError ("could not create memory BIO");
+       }
+       
+       RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
+       RSA* public_key = _certificates.leaf()->public_key ();
+       bool const valid = !BN_cmp (private_key->n, public_key->n);
+       BIO_free (bio);
+
+       return valid;
+}
index 92745ff2b105f5c7f5c241112d28c6be63613dc5..6fd17033f1c24d41a3652c09addd60d1a227acb6 100644 (file)
@@ -38,9 +38,11 @@ namespace dcp {
 /** @class Signer
  *  @brief A class which can sign XML files.
  */
-class Signer : public boost::noncopyable
+class Signer
 {
 public:
+       Signer (boost::filesystem::path openssl);
+       
        /** @param c Certificate chain to sign with.
         *  @param k Key to sign with as a PEM-format string.
         */
@@ -55,6 +57,20 @@ public:
        CertificateChain const & certificates () const {
                return _certificates;
        }
+
+       CertificateChain& certificates () {
+               return _certificates;
+       }
+       
+       std::string key () const {
+               return _key;
+       }
+
+       void set_key (std::string k) {
+               _key = k;
+       }
+
+       bool valid () const;
        
 private:       
 
diff --git a/src/signer_chain.cc b/src/signer_chain.cc
deleted file mode 100644 (file)
index 3b75b06..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
-    Copyright (C) 2013-2014 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.
-
-*/
-
-/** @file  src/signer_chain.cc
- *  @brief Functions to make signer chains.
- */
-
-#include "signer_chain.h"
-#include "exceptions.h"
-#include "util.h"
-#include "KM_util.h"
-#include <openssl/sha.h>
-#include <openssl/bio.h>
-#include <openssl/evp.h>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string.hpp>
-#include <fstream>
-#include <sstream>
-
-using std::string;
-using std::ofstream;
-using std::ifstream;
-using std::stringstream;
-using std::cout;
-
-/** Run a shell command.
- *  @param cmd Command to run (UTF8-encoded).
- */
-static void
-command (string cmd)
-{
-#ifdef LIBDCP_WINDOWS
-       /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
-          is handled correctly.
-       */
-       int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
-       wchar_t* buffer = new wchar_t[wn];
-       if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
-               delete[] buffer;
-               return;
-       }
-
-       int code = 1;
-
-       STARTUPINFOW startup_info;
-       memset (&startup_info, 0, sizeof (startup_info));
-       startup_info.cb = sizeof (startup_info);
-       PROCESS_INFORMATION process_info;
-
-       /* XXX: this doesn't actually seem to work; failing commands end up with
-          a return code of 0
-       */
-       if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
-               WaitForSingleObject (process_info.hProcess, INFINITE);
-               DWORD c;
-               if (GetExitCodeProcess (process_info.hProcess, &c)) {
-                       code = c;
-               }
-               CloseHandle (process_info.hProcess);
-               CloseHandle (process_info.hThread);
-       }
-
-       delete[] buffer;
-#else
-       cmd += " 2> /dev/null";
-       int const r = system (cmd.c_str ());
-       int const code = WEXITSTATUS (r);
-#endif
-       if (code) {
-               stringstream s;
-               s << "error " << code << " in " << cmd << " within " << boost::filesystem::current_path();
-               throw dcp::MiscError (s.str());
-       }
-}
-
-/** Extract a public key from a private key and create a SHA1 digest of it.
- *  @param private_key Private key
- *  @param openssl openssl binary name (or full path if openssl is not on the system path).
- *  @return SHA1 digest of corresponding public key, with escaped / characters.
- */
-static string
-public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
-{
-       boost::filesystem::path public_name = private_key.string() + ".public";
-
-       /* Create the public key from the private key */
-       stringstream s;
-       s << "\"" << openssl.string() << "\" rsa -outform PEM -pubout -in " << private_key.string() << " -out " << public_name.string ();
-       command (s.str().c_str ());
-
-       /* Read in the public key from the file */
-
-       string pub;
-       ifstream f (public_name.string().c_str ());
-       if (!f.good ()) {
-               throw dcp::MiscError ("public key not found");
-       }
-
-       bool read = false;
-       while (f.good ()) {
-               string line;
-               getline (f, line);
-               if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
-                       read = true;
-               } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
-                       break;
-               } else if (read) {
-                       pub += line;
-               }
-       }
-
-       /* Decode the base64 of the public key */
-               
-       unsigned char buffer[512];
-       int const N = dcp::base64_decode (pub, buffer, 1024);
-
-       /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
-
-       SHA_CTX context;
-       if (!SHA1_Init (&context)) {
-               throw dcp::MiscError ("could not init SHA1 context");
-       }
-
-       if (!SHA1_Update (&context, buffer + 24, N - 24)) {
-               throw dcp::MiscError ("could not update SHA1 digest");
-       }
-
-       unsigned char digest[SHA_DIGEST_LENGTH];
-       if (!SHA1_Final (digest, &context)) {
-               throw dcp::MiscError ("could not finish SHA1 digest");
-       }
-
-       char digest_base64[64];
-       string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
-#ifdef LIBDCP_WINDOWS
-       boost::replace_all (dig, "/", "\\/");
-#else  
-       boost::replace_all (dig, "/", "\\\\/");
-#endif 
-       return dig;
-}
-
-/** Generate a chain of root, intermediate and leaf keys by running an OpenSSL binary.
- *  @param directory Directory to write the files to.
- *  @param openssl openssl binary path.
- */
-void
-dcp::make_signer_chain (boost::filesystem::path directory, boost::filesystem::path openssl)
-{
-       boost::filesystem::path const cwd = boost::filesystem::current_path ();
-
-       string quoted_openssl = "\"" + openssl.string() + "\"";
-
-       boost::filesystem::current_path (directory);
-       command (quoted_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";
-       }
-
-       string const ca_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION/dnQualifier=" + public_key_digest ("ca.key", openssl);
-
-       {
-               stringstream c;
-               c << quoted_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 (quoted_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";
-       }
-               
-       string const inter_subject = "/O=example.org/OU=example.org/CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION/dnQualifier="
-               + public_key_digest ("intermediate.key", openssl);
-
-       {
-               stringstream s;
-               s << quoted_openssl
-                 << " req -new -config intermediate.cnf -days 3649 -subj " << inter_subject << " -key intermediate.key -out intermediate.csr";
-               command (s.str().c_str());
-       }
-
-       
-       command (
-               quoted_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 (quoted_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";
-       }
-
-       string const leaf_subject = "/O=example.org/OU=example.org/CN=CS.smpte-430-2.LEAF.NOT_FOR_PRODUCTION/dnQualifier="
-               + public_key_digest ("leaf.key", openssl);
-
-       {
-               stringstream s;
-               s << quoted_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 (
-               quoted_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"
-               );
-
-       boost::filesystem::current_path (cwd);
-}
diff --git a/src/signer_chain.h b/src/signer_chain.h
deleted file mode 100644 (file)
index f039caf..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    Copyright (C) 2013-2014 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.
-
-*/
-
-/** @file  src/signer_chain.h
- *  @brief Functions to make signer chains.
- */
-
-#include <boost/filesystem.hpp>
-
-namespace dcp {
-
-/** Create a chain of certificates for signing things.
- *  @param directory Directory to write files to.
- *  @param openssl Name of openssl binary (if it is on the path) or full path.
- */
-void make_signer_chain (boost::filesystem::path directory, boost::filesystem::path openssl);
-
-}
index e9b3c955c18279bfa0381dc0b1ab1a8bc3ddee9c..87059b36e4238af1c2ba8225e4425360929ce92d 100644 (file)
@@ -14,6 +14,7 @@ def build(bld):
     obj.source = """
                  argb_frame.cc
                  asset.cc
+                 certificate_chain.cc
                  certificates.cc
                  colour_matrix.cc
                  content.cc
@@ -49,7 +50,6 @@ def build(bld):
                  reel_subtitle_asset.cc
                  rgb_xyz.cc
                  signer.cc
-                 signer_chain.cc
                  sound_mxf.cc
                  sound_mxf_writer.cc
                  sound_frame.cc
@@ -68,6 +68,7 @@ def build(bld):
 
     headers = """
               asset.h
+              certificate_chain.h
               certificates.h
               colour_matrix.h
               cpl.h
@@ -103,7 +104,6 @@ def build(bld):
               ref.h
               argb_frame.h
               signer.h
-              signer_chain.h
               sound_frame.h
               sound_mxf.h
               sound_mxf_writer.h
index 3e3459480ee576c808111e4219ee95ebc661fef2..8cbe9527e39d0a73af3967330647a4e103bdb814 100644 (file)
 
 #include <boost/test/unit_test.hpp>
 #include "certificates.h"
+#include "signer.h"
+#include "util.h"
 
 using std::list;
+using std::cout;
+using std::string;
 using boost::shared_ptr;
 
 BOOST_AUTO_TEST_CASE (certificates)
@@ -31,9 +35,9 @@ BOOST_AUTO_TEST_CASE (certificates)
        c.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
        c.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
 
-       list<shared_ptr<dcp::Certificate> > leaf_to_root = c.leaf_to_root ();
+       dcp::CertificateChain::List leaf_to_root = c.leaf_to_root ();
 
-       list<shared_ptr<dcp::Certificate> >::iterator i = leaf_to_root.begin ();
+       dcp::CertificateChain::List::iterator i = leaf_to_root.begin ();
 
        /* Leaf */
        BOOST_CHECK_EQUAL (*i, c.leaf ());
@@ -82,49 +86,66 @@ BOOST_AUTO_TEST_CASE (certificates)
        BOOST_CHECK_EQUAL (test.certificate(), c.root()->certificate());
 }
 
-/** Check that dcp::CertificateChain::validate() and ::attempt_reorder() basically work */
+/** Check that dcp::CertificateChain::valid() and ::attempt_reorder() basically work */
 BOOST_AUTO_TEST_CASE (certificates_validation)
 {
        dcp::CertificateChain good1;
        good1.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
        good1.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
        good1.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
-       BOOST_CHECK (good1.verify ());
+       BOOST_CHECK (good1.valid ());
 
        dcp::CertificateChain good2;
        good2.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
-       BOOST_CHECK (good2.verify ());
+       BOOST_CHECK (good2.valid ());
        
        dcp::CertificateChain bad1;
        bad1.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
        bad1.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
-       BOOST_CHECK (!bad1.verify ());
+       BOOST_CHECK (!bad1.valid ());
        BOOST_CHECK (!bad1.attempt_reorder ());
 
        dcp::CertificateChain bad2;
        bad2.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
        bad2.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
        bad2.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
-       BOOST_CHECK (!bad2.verify ());
+       BOOST_CHECK (!bad2.valid ());
        BOOST_CHECK (bad2.attempt_reorder ());
 
        dcp::CertificateChain bad3;
        bad3.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
        bad3.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
        bad3.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
-       BOOST_CHECK (!bad3.verify ());
+       BOOST_CHECK (!bad3.valid ());
        BOOST_CHECK (bad3.attempt_reorder ());
 
        dcp::CertificateChain bad4;
        bad4.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
        bad4.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
        bad4.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
-       BOOST_CHECK (!bad4.verify ());
+       BOOST_CHECK (!bad4.valid ());
        BOOST_CHECK (bad4.attempt_reorder ());
 
        dcp::CertificateChain bad5;
        bad5.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
        bad5.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
-       BOOST_CHECK (!bad5.verify ());
+       BOOST_CHECK (!bad5.valid ());
        BOOST_CHECK (!bad5.attempt_reorder ());
 }
+
+/** Check that dcp::Signer::valid() basically works */
+BOOST_AUTO_TEST_CASE (signer_validation)
+{
+       /* Check a valid signer */
+       dcp::CertificateChain chain;
+       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/ca.self-signed.pem"))));
+       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/intermediate.signed.pem"))));
+       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("test/ref/crypt/leaf.signed.pem"))));
+       dcp::Signer signer (chain, dcp::file_to_string ("test/ref/crypt/leaf.key"));
+       BOOST_CHECK (signer.valid ());
+
+       /* Put in an unrelated key and the signer should no longer be valid */
+       dcp::Signer another_signer ("openssl");
+       signer.set_key (another_signer.key ());
+       BOOST_CHECK (!signer.valid ());
+}
index f80bb900b18907cecc07b7bf8e348b7dbbc3525e..b5077a46ce505b576f26ce1c0fcbf0c74d77ac42 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2014 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
@@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE (decryption_test)
 
        dcp::DecryptedKDM kdm (
                dcp::EncryptedKDM ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
-               "test/data/private.key"
+               dcp::file_to_string ("test/data/private.key")
                );
        
        encrypted.add (kdm);
@@ -81,6 +81,6 @@ BOOST_AUTO_TEST_CASE (failing_kdm_test)
 {
        dcp::DecryptedKDM kdm (
                dcp::EncryptedKDM ("test/data/target.pem.crt.de5d4eba-e683-41ca-bdda-aa4ad96af3f4.kdm.xml"),
-               "test/data/private.key"
+               dcp::file_to_string ("test/data/private.key")
                );
 }
index 600a4eaeb129d0505dce90e9731073706d54de26..6d2e052e99d9be22ed58c812081377791a941afa 100644 (file)
@@ -30,7 +30,6 @@
 #include "reel.h"
 #include "test.h"
 #include "file.h"
-#include "signer_chain.h"
 #include "subtitle_content.h"
 #include "reel_mono_picture_asset.h"
 #include "reel_sound_asset.h"
@@ -49,7 +48,6 @@ BOOST_AUTO_TEST_CASE (encryption_test)
 {
        boost::filesystem::remove_all ("build/test/signer");
        boost::filesystem::create_directory ("build/test/signer");
-       dcp::make_signer_chain ("build/test/signer", "openssl");
        
        Kumu::libdcp_test = true;
 
index 7de62f5a2aac9290b68ae96ed0ea394c99dd4f15..1fc76dbaf51625fce5673c83a0587b26f140eb96 100644 (file)
@@ -21,6 +21,7 @@
 #include <libxml++/libxml++.h>
 #include "encrypted_kdm.h"
 #include "decrypted_kdm.h"
+#include "util.h"
 
 using std::list;
 using std::stringstream;
@@ -30,7 +31,7 @@ BOOST_AUTO_TEST_CASE (kdm_test)
 {
        dcp::DecryptedKDM kdm (
                dcp::EncryptedKDM ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
-               "test/data/private.key"
+               dcp::file_to_string ("test/data/private.key")
                );
 
        list<dcp::DecryptedKDMKey> keys = kdm.keys ();
index 899734f48ac1893c7fe96c8884450f43e1e461f8..7ba501e60edffd0c042731c161ecd48dc529555d 100644 (file)
@@ -28,7 +28,7 @@
 #include "cpl.h"
 #include "mono_picture_frame.h"
 #include "argb_frame.h"
-#include "signer_chain.h"
+#include "certificate_chain.h"
 #include "mono_picture_mxf_writer.h"
 #include "reel_picture_asset.h"
 #include "reel_mono_picture_asset.h"
@@ -42,21 +42,7 @@ using boost::shared_ptr;
 /* Build an encrypted picture MXF and a KDM for it and check that the KDM can be decrypted */
 BOOST_AUTO_TEST_CASE (round_trip_test)
 {
-       boost::filesystem::remove_all ("build/test/signer");
-       boost::filesystem::create_directory ("build/test/signer");
-       dcp::make_signer_chain ("build/test/signer", "openssl");
-       
-       dcp::CertificateChain chain;
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/ca.self-signed.pem"))));
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/intermediate.signed.pem"))));
-       chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (boost::filesystem::path ("build/test/signer/leaf.signed.pem"))));
-
-       shared_ptr<dcp::Signer> signer (
-               new dcp::Signer (
-                       chain,
-                       dcp::file_to_string ("test/data/signer.key")
-                       )
-               );
+       shared_ptr<dcp::Signer> signer (new dcp::Signer ("openssl"));
 
        boost::filesystem::path work_dir = "build/test/round_trip_test";
        boost::filesystem::create_directory (work_dir);
@@ -93,7 +79,7 @@ BOOST_AUTO_TEST_CASE (round_trip_test)
        kdm_A.encrypt(signer, signer->certificates().leaf(), dcp::MODIFIED_TRANSITIONAL_1).as_xml (kdm_file);
 
        /* Reload the KDM, using our private key to decrypt it */
-       dcp::DecryptedKDM kdm_B (dcp::EncryptedKDM (kdm_file), "build/test/signer/leaf.key");
+       dcp::DecryptedKDM kdm_B (dcp::EncryptedKDM (kdm_file), signer->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());