Merge master.
[libdcp.git] / src / signer_chain.cc
index f538ce4f68f0772f99b7ce7d406ba43b02f0ff5c..3b75b06ccae717939c46fea3bf52a6b2417682fa 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
 
 */
 
-#include <fstream>
-#include <sstream>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string.hpp>
-#include <openssl/sha.h>
-#include <openssl/bio.h>
-#include <openssl/evp.h>
-#include "KM_util.h"
+/** @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;
@@ -35,44 +39,81 @@ using std::ifstream;
 using std::stringstream;
 using std::cout;
 
-static void command (string cmd)
+/** Run a shell command.
+ *  @param cmd Command to run (UTF8-encoded).
+ */
+static void
+command (string cmd)
 {
-       int const r = system (cmd.c_str ());
-#ifdef LIBDCP_WINDOWS  
-       int const code = r;
+#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 libdcp::MiscError (s.str());
+               throw dcp::MiscError (s.str());
        }
 }
 
 /** Extract a public key from a private key and create a SHA1 digest of it.
- *  @param key Private key
+ *  @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() << " > " << public_name.string ();
+       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 (1) {
+       while (f.good ()) {
                string line;
                getline (f, line);
                if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
@@ -87,22 +128,22 @@ public_key_digest (boost::filesystem::path private_key, boost::filesystem::path
        /* Decode the base64 of the public key */
                
        unsigned char buffer[512];
-       int const N = libdcp::base64_decode (pub, buffer, 1024);
+       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 libdcp::MiscError ("could not init SHA1 context");
+               throw dcp::MiscError ("could not init SHA1 context");
        }
 
        if (!SHA1_Update (&context, buffer + 24, N - 24)) {
-               throw libdcp::MiscError ("could not update SHA1 digest");
+               throw dcp::MiscError ("could not update SHA1 digest");
        }
 
        unsigned char digest[SHA_DIGEST_LENGTH];
        if (!SHA1_Final (digest, &context)) {
-               throw libdcp::MiscError ("could not finish SHA1 digest");
+               throw dcp::MiscError ("could not finish SHA1 digest");
        }
 
        char digest_base64[64];
@@ -115,13 +156,19 @@ public_key_digest (boost::filesystem::path private_key, boost::filesystem::path
        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
-libdcp::make_signer_chain (boost::filesystem::path directory, boost::filesystem::path openssl)
+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 (openssl.string() + " genrsa -out ca.key 2048");
+       command (quoted_openssl + " genrsa -out ca.key 2048");
 
        {
                ofstream f ("ca.cnf");
@@ -143,13 +190,13 @@ libdcp::make_signer_chain (boost::filesystem::path directory, boost::filesystem:
 
        {
                stringstream c;
-               c << openssl.string()
+               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 (openssl.string() + " genrsa -out intermediate.key 2048");
+       command (quoted_openssl + " genrsa -out intermediate.key 2048");
 
        {
                ofstream f ("intermediate.cnf");
@@ -172,18 +219,19 @@ libdcp::make_signer_chain (boost::filesystem::path directory, boost::filesystem:
 
        {
                stringstream s;
-               s << openssl.string() << " req -new -config intermediate.cnf -days 3649 -subj " << inter_subject << " -key intermediate.key -out intermediate.csr";
+               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 (
-               openssl.string() +
+               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 (openssl.string() + " genrsa -out leaf.key 2048");
+       command (quoted_openssl + " genrsa -out leaf.key 2048");
 
        {
                ofstream f ("leaf.cnf");
@@ -206,12 +254,12 @@ libdcp::make_signer_chain (boost::filesystem::path directory, boost::filesystem:
 
        {
                stringstream s;
-               s << openssl.string() << " req -new -config leaf.cnf -days 3648 -subj " << leaf_subject << " -key leaf.key -outform PEM -out leaf.csr";
+               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 (
-               openssl.string() +
+               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"
                );