Allow reading of certificate chains from strings.
authorCarl Hetherington <cth@carlh.net>
Thu, 25 Aug 2016 13:00:33 +0000 (14:00 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 25 Aug 2016 13:00:33 +0000 (14:00 +0100)
This also makes the Certificate constructor throw if it finds
extra stuff after a certificate it is loading.

src/certificate.cc
src/certificate.h
src/certificate_chain.cc
src/certificate_chain.h
test/certificates_test.cc
test/round_trip_test.cc

index a30b77cddbe2677f02a294938182e515bb2d66b1..fbe3a80d2c2c9b6667f2085a704749d015df82f8 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -64,7 +64,6 @@ static string const end_certificate = "-----END CERTIFICATE-----";
 Certificate::Certificate (X509* c)
        : _certificate (c)
        , _public_key (0)
-       , _extra_data (false)
 {
 
 }
@@ -76,7 +75,10 @@ Certificate::Certificate (string cert)
        : _certificate (0)
        , _public_key (0)
 {
-       _extra_data = read_string (cert);
+       string const s = read_string (cert);
+       if (!s.empty ()) {
+               throw MiscError ("unexpected data after certificate");
+       }
 }
 
 /** Copy constructor.
@@ -85,7 +87,6 @@ Certificate::Certificate (string cert)
 Certificate::Certificate (Certificate const & other)
        : _certificate (0)
        , _public_key (0)
-       , _extra_data (other._extra_data)
 {
        if (other._certificate) {
                read_string (other.certificate (true));
@@ -94,9 +95,9 @@ Certificate::Certificate (Certificate const & other)
 
 /** Read a certificate from a string.
  *  @param cert String to read.
- *  @return true if there is extra stuff after the end of the certificate, false if not.
+ *  @return remaining part of the input string after the certificate which was read.
  */
-bool
+string
 Certificate::read_string (string cert)
 {
        /* Reformat cert so that it has line breaks every 64 characters.
@@ -176,11 +177,16 @@ Certificate::read_string (string cert)
 
        BIO_free (bio);
 
-       /* See if there are any non-blank lines after the certificate that we read */
-       while (i != lines.end() && i->empty()) {
+       string extra;
+
+       while (i != lines.end()) {
+               if (!i->empty()) {
+                       extra += *i + "\n";
+               }
                ++i;
        }
-       return i != lines.end();
+
+       return extra;
 }
 
 /** Destructor */
@@ -204,7 +210,6 @@ Certificate::operator= (Certificate const & other)
        _certificate = 0;
        RSA_free (_public_key);
        _public_key = 0;
-       _extra_data = other._extra_data;
 
        read_string (other.certificate (true));
 
index 6193af8fe59315845094b7586b89c8522ae38fd5..8237c739e726200ad74c58ec3177c1df72984337 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -63,7 +63,6 @@ public:
        Certificate ()
                : _certificate (0)
                , _public_key (0)
-               , _extra_data (false)
        {}
 
        explicit Certificate (std::string);
@@ -73,6 +72,8 @@ public:
 
        Certificate& operator= (Certificate const &);
 
+       std::string read_string (std::string);
+
        std::string certificate (bool with_begin_end = false) const;
        std::string serial () const;
 
@@ -91,12 +92,7 @@ public:
 
        std::string thumbprint () const;
 
-       bool extra_data () const {
-               return _extra_data;
-       }
-
 private:
-       bool read_string (std::string);
 
        static std::string name_for_xml (X509_NAME *);
        static std::string asn_to_utf8 (ASN1_STRING *);
@@ -104,10 +100,6 @@ private:
 
        X509* _certificate;
        mutable RSA* _public_key;
-       /** true if extra data was found when this certificate was read
-           from a string.
-       */
-       bool _extra_data;
 };
 
 bool operator== (Certificate const & a, Certificate const & b);
index df68cb9ffb0fa64926002c64a22e44c7c69dc02d..ac01d860d3c5c567aaa643ed29b714f6e2266f69 100644 (file)
@@ -55,6 +55,7 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <fstream>
+#include <iostream>
 
 using std::string;
 using std::ofstream;
@@ -312,6 +313,24 @@ CertificateChain::CertificateChain (
        boost::filesystem::remove_all (directory);
 }
 
+CertificateChain::CertificateChain (string s)
+{
+       while (true) {
+               try {
+                       Certificate c;
+                       s = c.read_string (s);
+                       _certificates.push_back (c);
+               } catch (MiscError& e) {
+                       /* Failed to read a certificate, just stop */
+                       break;
+               }
+       }
+
+       if (!attempt_reorder ()) {
+               throw MiscError ("could not find certificate chain order");
+       }
+}
+
 /** @return Root certificate */
 Certificate
 CertificateChain::root () const
index ca259c088e0e5d5b6c532a971f4d78acf9916ee8..9d7ab47c1cf6e676f374a2a78b626ceaaec5dc08 100644 (file)
@@ -74,6 +74,8 @@ public:
                std::string leaf_common_name = "CS.smpte-430-2.LEAF.NOT_FOR_PRODUCTION"
                );
 
+       explicit CertificateChain (std::string);
+
        void add (Certificate c);
        void remove (Certificate c);
        void remove (int);
index 879a89dda6535069214889bfea258b66b489ef30..8ae01eed6ee615e8583b0a3fad77c50216fe58da 100644 (file)
@@ -55,8 +55,6 @@ BOOST_AUTO_TEST_CASE (certificates1)
                "dnQualifier=QFVlym7fuql6bPOnY38aaO1ZPW4=,CN=CS.smpte-430-2.LEAF.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
                );
 
-       BOOST_CHECK (!c.leaf().extra_data ());
-
        ++i;
 
        /* Intermediate */
@@ -70,8 +68,6 @@ BOOST_AUTO_TEST_CASE (certificates1)
                "dnQualifier=6eat8r33US71avuQEojmH\\+bjk84=,CN=.smpte-430-2.INTERMEDIATE.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
                );
 
-       BOOST_CHECK (!i->extra_data ());
-
        ++i;
 
        /* Root */
@@ -88,8 +84,6 @@ BOOST_AUTO_TEST_CASE (certificates1)
                "dnQualifier=DCnRdHFbcv4ANVUq2\\+wMVALFSec=,CN=.smpte-430-2.ROOT.NOT_FOR_PRODUCTION,OU=example.org,O=example.org"
                );
 
-       BOOST_CHECK (!c.root().extra_data ());
-
        /* Check that reconstruction from a string works */
        dcp::Certificate test (c.root().certificate (true));
        BOOST_CHECK_EQUAL (test.certificate(), c.root().certificate());
@@ -101,18 +95,16 @@ BOOST_AUTO_TEST_CASE (certificates2)
        {
                dcp::Certificate c (dcp::file_to_string (private_test / "CA.GDC-TECH.COM_SA2100_A14903.crt.crt"));
                BOOST_CHECK_EQUAL (c.certificate(true), dcp::file_to_string (private_test / "CA.GDC-TECH.COM_SA2100_A14903.crt.crt.reformatted"));
-               BOOST_CHECK (!c.extra_data ());
        }
 
        {
                dcp::Certificate c (dcp::file_to_string (private_test / "usl-cert.pem"));
                BOOST_CHECK_EQUAL (c.certificate(true), dcp::file_to_string (private_test / "usl-cert.pem.trimmed"));
-               BOOST_CHECK (!c.extra_data ());
        }
 
        {
-               dcp::Certificate c (dcp::file_to_string (private_test / "chain.pem"));
-               BOOST_CHECK (c.extra_data ());
+               /* This is a chain, not an individual certificate, so it should throw an exception */
+               BOOST_CHECK_THROW (dcp::Certificate (dcp::file_to_string (private_test / "chain.pem")), dcp::MiscError);
        }
 
        BOOST_CHECK_THROW (dcp::Certificate (dcp::file_to_string (private_test / "no-begin.pem")), dcp::MiscError);
@@ -178,7 +170,17 @@ BOOST_AUTO_TEST_CASE (signer_validation)
        BOOST_CHECK (chain.valid ());
 
        /* Put in an unrelated key and the signer should no longer be valid */
-       dcp::CertificateChain another_chain ("openssl");
+       dcp::CertificateChain another_chain (boost::filesystem::path ("openssl"));
        chain.set_key (another_chain.key().get ());
        BOOST_CHECK (!chain.valid ());
 }
+
+/** Check reading of a certificate chain from a string */
+BOOST_AUTO_TEST_CASE (certificate_chain_from_string)
+{
+       dcp::CertificateChain a (dcp::file_to_string (private_test / "chain.pem"));
+       BOOST_CHECK_EQUAL (a.root_to_leaf().size(), 3);
+
+       dcp::CertificateChain b (dcp::file_to_string ("test/ref/crypt/leaf.signed.pem"));
+       BOOST_CHECK_EQUAL (b.root_to_leaf().size(), 1);
+}
index f149c5f14e23e25cb0784d92da63d4229c3e6b91..e2b902443818044aa6799b132b6f75248eb938a7 100644 (file)
@@ -48,7 +48,7 @@ using boost::scoped_array;
 /** Build an encrypted picture asset and a KDM for it and check that the KDM can be decrypted */
 BOOST_AUTO_TEST_CASE (round_trip_test)
 {
-       shared_ptr<dcp::CertificateChain> signer (new dcp::CertificateChain ("openssl"));
+       shared_ptr<dcp::CertificateChain> signer (new dcp::CertificateChain (boost::filesystem::path ("openssl")));
 
        boost::filesystem::path work_dir = "build/test/round_trip_test";
        boost::filesystem::create_directory (work_dir);