Tidying.
[libdcp.git] / src / decrypted_kdm.cc
index 7e9e146d506fc44d15907e7e1a6d31cfbde2409d..174efb1b983de9865a40c240471d8eef8294fbbc 100644 (file)
@@ -1,47 +1,77 @@
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
 
-    This program is free software; you can redistribute it and/or modify
+    This file is part of libdcp.
+
+    libdcp 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,
+    libdcp 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.
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
 
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
 */
 
+
+/** @file  src/decrypted_kdm.cc
+ *  @brief DecryptedKDM class
+ */
+
+
 #include "decrypted_kdm.h"
 #include "decrypted_kdm_key.h"
 #include "encrypted_kdm.h"
+#include "reel_mxf.h"
+#include "reel_asset.h"
 #include "util.h"
 #include "exceptions.h"
 #include "cpl.h"
-#include "mxf.h"
-#include "signer.h"
-#include "AS_DCP.h"
-#include "KM_util.h"
+#include "certificate_chain.h"
+#include "dcp_assert.h"
 #include "compose.hpp"
+#include <asdcp/AS_DCP.h>
+#include <asdcp/KM_util.h>
 #include <openssl/rsa.h>
 #include <openssl/pem.h>
 #include <openssl/err.h>
 
+
 using std::list;
+using std::vector;
 using std::string;
-using std::stringstream;
 using std::setw;
 using std::setfill;
 using std::hex;
 using std::pair;
-using boost::shared_ptr;
+using std::map;
+using std::shared_ptr;
+using boost::optional;
 using namespace dcp;
 
+
+/* Magic value specified by SMPTE S430-1-2006 */
+static uint8_t smpte_structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab };
+
+
 static void
 put (uint8_t ** d, string s)
 {
@@ -49,6 +79,7 @@ put (uint8_t ** d, string s)
         (*d) += s.length();
 }
 
+
 static void
 put (uint8_t ** d, uint8_t const * s, int N)
 {
@@ -56,36 +87,46 @@ put (uint8_t ** d, uint8_t const * s, int N)
         (*d) += N;
 }
 
-static void
-put_uuid (uint8_t ** d, string id)
+
+void
+DecryptedKDM::put_uuid (uint8_t ** d, string id)
 {
-        id.erase (std::remove (id.begin(), id.end(), '-'));
-        for (int i = 0; i < 32; i += 2) {
-                stringstream s;
-                s << id[i] << id[i + 1];
-                int h;
-                s >> hex >> h;
-                **d = h;
-                (*d)++;
-        }
+       /* 32 hex digits plus some hyphens */
+       DCP_ASSERT (id.length() == 36);
+#ifdef LIBDCP_WINDOWS
+       __mingw_sscanf (
+#else
+       sscanf (
+#endif
+               id.c_str(),
+               "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
+               *d + 0, *d + 1, *d + 2, *d + 3, *d + 4, *d + 5, *d + 6, *d + 7,
+               *d + 8, *d + 9, *d + 10, *d + 11, *d + 12, *d + 13, *d + 14, *d + 15
+               );
+
+       *d += 16;
 }
 
-static string
-get_uuid (unsigned char ** p)
+
+string
+DecryptedKDM::get_uuid (unsigned char ** p)
 {
-       stringstream g;
-       
-       for (int i = 0; i < 16; ++i) {
-               g << setw(2) << setfill('0') << hex << static_cast<int> (**p);
-               (*p)++;
-               if (i == 3 || i == 5 || i == 7 || i == 9) {
-                       g << '-';
-               }
-       }
+       char buffer[37];
+#ifdef LIBDCP_WINDOWS
+       __mingw_snprintf (
+#else
+       snprintf (
+#endif
+               buffer, sizeof(buffer), "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
+               (*p)[0], (*p)[1], (*p)[2], (*p)[3], (*p)[4], (*p)[5], (*p)[6], (*p)[7],
+               (*p)[8], (*p)[9], (*p)[10], (*p)[11], (*p)[12], (*p)[13], (*p)[14], (*p)[15]
+               );
 
-       return g.str ();
+       *p += 16;
+       return buffer;
 }
 
+
 static string
 get (uint8_t ** p, int N)
 {
@@ -98,36 +139,38 @@ 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);
+
+       auto 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);      
+
+       auto rsa = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
        if (!rsa) {
                throw FileError ("could not read RSA private key file", private_key, errno);
        }
 
        /* Use the private key to decrypt the keys */
 
-       list<string> const encrypted_keys = kdm.keys ();
-       for (list<string>::const_iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
-
+       for (auto const& i: kdm.keys()) {
                /* Decode the base-64-encoded cipher value from the KDM */
                unsigned char cipher_value[256];
-               int const cipher_value_len = base64_decode (*i, cipher_value, sizeof (cipher_value));
+               int const cipher_value_len = base64_decode (i, cipher_value, sizeof (cipher_value));
 
                /* Decrypt it */
-               unsigned char * decrypted = new unsigned char[RSA_size(rsa)];
+               auto decrypted = new unsigned char[RSA_size(rsa)];
                int const decrypted_len = RSA_private_decrypt (cipher_value_len, cipher_value, decrypted, rsa, RSA_PKCS1_OAEP_PADDING);
                if (decrypted_len == -1) {
                        delete[] decrypted;
-                       throw MiscError (String::compose ("Could not decrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0)));
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+                       throw KDMDecryptionError (ERR_error_string (ERR_get_error(), 0), cipher_value_len, RSA_bits(rsa));
+#else
+                       throw KDMDecryptionError (ERR_error_string (ERR_get_error(), 0), cipher_value_len, rsa->n->dmax);
+#endif
                }
 
                unsigned char* p = decrypted;
@@ -148,13 +191,14 @@ DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path pr
                        /* 93 is not-valid-after (a string) [25 bytes] */
                        p += 25;
                        /* 118 is the key [ASDCP::KeyLen bytes] */
-                       _keys.push_back (DecryptedKDMKey ("", key_id, Key (p), cpl_id));
+                       add_key (optional<string>(), key_id, Key(p), cpl_id, Standard::INTEROP);
                        break;
                }
                case 138:
                {
                        /* SMPTE */
                        /* 0 is structure id (fixed sequence specified by standard) [16 bytes] */
+                       DCP_ASSERT (memcmp (p, smpte_structure_id, 16) == 0);
                        p += 16;
                        /* 16 is is signer thumbprint [20 bytes] */
                        p += 20;
@@ -169,21 +213,26 @@ DecryptedKDM::DecryptedKDM (EncryptedKDM const & kdm, boost::filesystem::path pr
                        /* 97 is not-valid-after (a string) [25 bytes] */
                        p += 25;
                        /* 112 is the key [ASDCP::KeyLen bytes] */
-                       _keys.push_back (DecryptedKDMKey (key_type, key_id, Key (p), cpl_id));
+                       add_key (key_type, key_id, Key(p), cpl_id, Standard::SMPTE);
                        break;
                }
                default:
-                       assert (false);
-               }               
-               
+                       DCP_ASSERT (false);
+               }
+
                delete[] decrypted;
        }
 
        RSA_free (rsa);
+       BIO_free (bio);
+
+       _annotation_text = kdm.annotation_text ();
+       _content_title_text = kdm.content_title_text ();
+       _issue_date = kdm.issue_date ();
 }
 
+
 DecryptedKDM::DecryptedKDM (
-       boost::shared_ptr<const CPL> cpl,
        LocalTime not_valid_before,
        LocalTime not_valid_after,
        string annotation_text,
@@ -196,68 +245,142 @@ DecryptedKDM::DecryptedKDM (
        , _content_title_text (content_title_text)
        , _issue_date (issue_date)
 {
-       /* Create DecryptedKDMKey objects for each MXF asset */
-       list<shared_ptr<const Content> > content = cpl->content ();
-       for (list<shared_ptr<const Content> >::iterator i = content.begin(); i != content.end(); ++i) {
-               /* XXX: do non-MXF assets need keys? */
-               shared_ptr<const MXF> mxf = boost::dynamic_pointer_cast<const MXF> (*i);
-               if (mxf) {
-                       _keys.push_back (DecryptedKDMKey (mxf->key_type(), mxf->key_id(), mxf->key().get (), cpl->id ()));
+
+}
+
+
+DecryptedKDM::DecryptedKDM (
+       string cpl_id,
+       map<shared_ptr<const ReelMXF>, Key> keys,
+       LocalTime not_valid_before,
+       LocalTime not_valid_after,
+       string annotation_text,
+       string content_title_text,
+       string issue_date
+       )
+       : _not_valid_before (not_valid_before)
+       , _not_valid_after (not_valid_after)
+       , _annotation_text (annotation_text)
+       , _content_title_text (content_title_text)
+       , _issue_date (issue_date)
+{
+       for (map<shared_ptr<const ReelMXF>, Key>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
+               add_key (i->first->key_type(), i->first->key_id().get(), i->second, cpl_id, Standard::SMPTE);
+       }
+}
+
+
+DecryptedKDM::DecryptedKDM (
+       shared_ptr<const CPL> cpl,
+       Key key,
+       LocalTime not_valid_before,
+       LocalTime not_valid_after,
+       string annotation_text,
+       string content_title_text,
+       string issue_date
+       )
+       : _not_valid_before (not_valid_before)
+       , _not_valid_after (not_valid_after)
+       , _annotation_text (annotation_text)
+       , _content_title_text (content_title_text)
+       , _issue_date (issue_date)
+{
+       /* Create DecryptedKDMKey objects for each encryptable asset */
+       bool did_one = false;
+       for (auto i: cpl->reel_mxfs()) {
+               if (i->key_id()) {
+                       add_key (i->key_type(), i->key_id().get(), key, cpl->id(), Standard::SMPTE);
+                       did_one = true;
                }
        }
+
+       if (!did_one) {
+               throw NotEncryptedError (cpl->id ());
+       }
+}
+
+
+void
+DecryptedKDM::add_key (optional<string> type, string key_id, Key key, string cpl_id, Standard standard)
+{
+       _keys.push_back (DecryptedKDMKey (type, key_id, key, cpl_id, standard));
+}
+
+
+void
+DecryptedKDM::add_key (DecryptedKDMKey key)
+{
+       _keys.push_back (key);
 }
 
+
 EncryptedKDM
-DecryptedKDM::encrypt (shared_ptr<const Signer> signer, shared_ptr<const Certificate> recipient) const
+DecryptedKDM::encrypt (
+       shared_ptr<const CertificateChain> signer,
+       Certificate recipient,
+       vector<string> trusted_devices,
+       Formulation formulation,
+       bool disable_forensic_marking_picture,
+       optional<int> disable_forensic_marking_audio
+       ) const
 {
-       list<pair<string, string> > key_ids;
-       list<string> keys;
-       for (list<DecryptedKDMKey>::const_iterator i = _keys.begin(); i != _keys.end(); ++i) {
+       DCP_ASSERT (!_keys.empty ());
+
+       for (auto i: signer->leaf_to_root()) {
+               if (day_greater_than_or_equal(dcp::LocalTime(i.not_before()), _not_valid_before)) {
+                       throw BadKDMDateError (true);
+               } else if (day_less_than_or_equal(dcp::LocalTime(i.not_after()), _not_valid_after)) {
+                       throw BadKDMDateError (false);
+               }
+       }
 
-               key_ids.push_back (make_pair (i->type(), i->id ()));
+       vector<pair<string, string>> key_ids;
+       vector<string> keys;
+       for (auto const& i: _keys) {
+               /* We're making SMPTE keys so we must have a type for each one */
+               DCP_ASSERT (i.type());
+               key_ids.push_back (make_pair (i.type().get(), i.id ()));
 
                /* XXX: SMPTE only */
                uint8_t block[138];
                uint8_t* p = block;
 
-               /* Magic value specified by SMPTE S430-1-2006 */
-               uint8_t structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab };
-               put (&p, structure_id, 16);
+               put (&p, smpte_structure_id, 16);
 
-               base64_decode (signer->certificates().leaf()->thumbprint (), p, 20);
+               base64_decode (signer->leaf().thumbprint (), p, 20);
                p += 20;
-               
-               put_uuid (&p, i->cpl_id ());
-               put (&p, i->type ());
-               put_uuid (&p, i->id ());
+
+               put_uuid (&p, i.cpl_id ());
+               put (&p, i.type().get());
+               put_uuid (&p, i.id ());
                put (&p, _not_valid_before.as_string ());
                put (&p, _not_valid_after.as_string ());
-               put (&p, i->key().value(), ASDCP::KeyLen);
-               
+               put (&p, i.key().value(), ASDCP::KeyLen);
+
                /* Encrypt using the projector's public key */
-               RSA* rsa = recipient->public_key ();
+               RSA* rsa = recipient.public_key ();
                unsigned char encrypted[RSA_size(rsa)];
                int const encrypted_len = RSA_public_encrypt (p - block, block, encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
                if (encrypted_len == -1) {
                        throw MiscError (String::compose ("Could not encrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0)));
                }
-               
+
                /* Lazy overallocation */
                char out[encrypted_len * 2];
                Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2);
                int const N = strlen (out);
-               stringstream lines;
+               string lines;
                for (int i = 0; i < N; ++i) {
                        if (i > 0 && (i % 64) == 0) {
-                               lines << "\n";
+                               lines += "\n";
                        }
-                       lines << out[i];
+                       lines += out[i];
                }
-               
-               keys.push_back (lines.str ());
+
+               keys.push_back (lines);
        }
 
-       string device_list_description = recipient->common_name ();
+       string device_list_description = recipient.subject_common_name ();
        if (device_list_description.find (".") != string::npos) {
                device_list_description = device_list_description.substr (device_list_description.find (".") + 1);
        }
@@ -265,11 +388,15 @@ DecryptedKDM::encrypt (shared_ptr<const Signer> signer, shared_ptr<const Certifi
        return EncryptedKDM (
                signer,
                recipient,
-               device_list_description,
+               trusted_devices,
                _keys.front().cpl_id (),
                _content_title_text,
+               _annotation_text,
                _not_valid_before,
                _not_valid_after,
+               formulation,
+               disable_forensic_marking_picture,
+               disable_forensic_marking_audio,
                key_ids,
                keys
                );