Browse Source

LibTLS: (Almost) verify certificate chain against root CA certificates

Also adds a very primitive systemwide ca_certs.ini file.
AnotherTest 4 năm trước cách đây
mục cha
commit
37c089fb7b

+ 465 - 0
Base/etc/ca_certs.ini

@@ -0,0 +1,465 @@
+
+[PKIACCV]
+country=ES
+
+[AC RAIZ FNMT-RCM]
+country=ES
+
+[Actalis Authentication Root CA]
+country=IT
+
+[AddTrust External TTP Network]
+country=SE
+
+[AddTrust TTP Network]
+country=SE
+
+[AffirmTrust Commercial]
+country=US
+
+[AffirmTrust Networking]
+country=US
+
+[AffirmTrust Premium]
+country=US
+
+[AffirmTrust Premium ECC]
+country=US
+
+[Amazon Root CA 1]
+country=US
+
+[Amazon Root CA 2]
+country=US
+
+[Amazon Root CA 3]
+country=US
+
+[Amazon Root CA 4]
+country=US
+
+[Atos TrustedRoot 2011]
+country=DE
+
+[Autoridad de Certificacion Firmaprofesional CIF A62634068]
+country=ES
+
+[CyberTrust]
+country=IE
+
+[Buypass Class 2 Root CA]
+country=NO
+
+[Buypass Class 3 Root CA]
+country=NO
+
+[CA Disig Root R2]
+country=SK
+
+[CFCA EV ROOT]
+country=CN
+
+[COMODO Certification Authority]
+country=GB
+
+[COMODO ECC Certification Authority]
+country=GB
+
+[COMODO RSA Certification Authority]
+country=GB
+
+[http://www.chambersign.org]
+country=EU
+
+[http://www.chambersign.org]
+country=EU
+
+[Certigna]
+country=FR
+
+[0002 48146308100036]
+country=FR
+
+[0002 433998903]
+country=FR
+
+[Class 2 Primary CA]
+country=FR
+
+[Certum CA]
+country=PL
+
+[Certum Certification Authority]
+country=PL
+
+[Certum Certification Authority]
+country=PL
+
+[Chambers of Commerce Root - 2008]
+country=EU
+
+[AAA Certificate Services]
+country=GB
+
+[Cybertrust Global Root]
+country=
+
+[D-TRUST Root CA 3 2013]
+country=DE
+
+[D-TRUST Root Class 3 CA 2 2009]
+country=DE
+
+[D-TRUST Root Class 3 CA 2 EV 2009]
+country=DE
+
+[DST Root CA X3]
+country=
+
+[T-TeleSec Trust Center]
+country=DE
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[www.digicert.com]
+country=US
+
+[E-Tugra Sertifikasyon Merkezi]
+country=TR
+
+[Serveis Publics de Certificacio]
+country=ES
+
+[EE Certification Centre Root CA]
+country=EE
+
+[www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)]
+country=
+
+[www.entrust.net/CPS is incorporated by reference]
+country=US
+
+[See www.entrust.net/legal-terms]
+country=US
+
+[See www.entrust.net/legal-terms]
+country=US
+
+[DigiNotar Root CA]
+country=NL
+
+[DigiNotar PKIoverheid CA Organisatie - G2]
+country=NL
+
+[GDCA TrustAUTH R5 ROOT]
+country=CN
+
+[GTS Root R1]
+country=US
+
+[GTS Root R2]
+country=US
+
+[GTS Root R3]
+country=US
+
+[GTS Root R4]
+country=US
+
+[GeoTrust Global CA]
+country=US
+
+[GeoTrust Primary Certification Authority]
+country=US
+
+[(c) 2007 GeoTrust Inc. - For authorized use only]
+country=US
+
+[(c) 2008 GeoTrust Inc. - For authorized use only]
+country=US
+
+[GeoTrust Universal CA]
+country=US
+
+[GeoTrust Universal CA 2]
+country=US
+
+[GlobalSign ECC Root CA - R4]
+country=
+
+[GlobalSign ECC Root CA - R5]
+country=
+
+[Root CA]
+country=BE
+
+[GlobalSign Root CA - R2]
+country=
+
+[GlobalSign Root CA - R3]
+country=
+
+[GlobalSign Root CA - R6]
+country=
+
+[Global Chambersign Root - 2008]
+country=EU
+
+[Go Daddy Class 2 Certification Authority]
+country=US
+
+[Go Daddy Root Certificate Authority - G2]
+country=US
+
+[Hellenic Academic and Research Institutions ECC RootCA 2015]
+country=GR
+
+[Hellenic Academic and Research Institutions RootCA 2011]
+country=GR
+
+[Hellenic Academic and Research Institutions RootCA 2015]
+country=GR
+
+[Hongkong Post Root CA 1]
+country=HK
+
+[Hongkong Post Root CA 3]
+country=HK
+
+[ISRG Root X1]
+country=US
+
+[IdenTrust Commercial Root CA 1]
+country=US
+
+[IdenTrust Public Sector Root CA 1]
+country=US
+
+[Izenpe.com]
+country=ES
+
+[LuxTrust Global Root 2]
+country=LU
+
+[Microsec e-Szigno Root CA 2009]
+country=HU
+
+[Tan\C3\BAs\C3\ADtv\C3\A1nykiad\C3\B3k (Certification Services)]
+country=HU
+
+[Network Solutions Certificate Authority]
+country=US
+
+[Copyright (c) 2005]
+country=CH
+
+[OISTE Foundation Endorsed]
+country=CH
+
+[OISTE Foundation Endorsed]
+country=CH
+
+[Root Certification Authority]
+country=BM
+
+[QuoVadis Root CA 1 G3]
+country=BM
+
+[QuoVadis Root CA 2]
+country=BM
+
+[QuoVadis Root CA 2 G3]
+country=BM
+
+[QuoVadis Root CA 3]
+country=BM
+
+[QuoVadis Root CA 3 G3]
+country=BM
+
+[SSL.com EV Root Certification Authority ECC]
+country=US
+
+[SSL.com EV Root Certification Authority RSA R2]
+country=US
+
+[SSL.com Root Certification Authority ECC]
+country=US
+
+[SSL.com Root Certification Authority RSA]
+country=US
+
+[SZAFIR ROOT CA2]
+country=PL
+
+[SecureSign RootCA11]
+country=JP
+
+[SecureTrust CA]
+country=US
+
+[Secure Global CA]
+country=US
+
+[Security Communication RootCA2]
+country=JP
+
+[Security Communication RootCA1]
+country=JP
+
+[Sonera Class2 CA]
+country=FI
+
+[Staat der Nederlanden EV Root CA]
+country=NL
+
+[Staat der Nederlanden Root CA - G2]
+country=NL
+
+[Staat der Nederlanden Root CA - G3]
+country=NL
+
+[Starfield Class 2 Certification Authority]
+country=US
+
+[Starfield Root Certificate Authority - G2]
+country=US
+
+[Starfield Services Root Certificate Authority - G2]
+country=US
+
+[SwissSign Gold CA - G2]
+country=CH
+
+[SwissSign Platinum CA - G2]
+country=CH
+
+[SwissSign Silver CA - G2]
+country=CH
+
+[Digital Certificate Services]
+country=ch
+
+[Symantec Trust Network]
+country=US
+
+[Symantec Trust Network]
+country=US
+
+[Symantec Trust Network]
+country=US
+
+[Symantec Trust Network]
+country=US
+
+[T-Systems Trust Center]
+country=DE
+
+[T-Systems Trust Center]
+country=DE
+
+[Kamu Sertifikasyon Merkezi - Kamu SM]
+country=TR
+
+[Root CA]
+country=TW
+
+[Root CA]
+country=TW
+
+[]
+country=TW
+
+[TeliaSonera Root CA v1]
+country=
+
+[TrustCor Certificate Authority]
+country=PA
+
+[TrustCor Certificate Authority]
+country=PA
+
+[TrustCor Certificate Authority]
+country=PA
+
+[Trustis FPS Root CA]
+country=GB
+
+[UCA Extended Validation Root]
+country=CN
+
+[UCA Global G2 Root]
+country=CN
+
+[USERTrust ECC Certification Authority]
+country=US
+
+[USERTrust RSA Certification Authority]
+country=US
+
+[http://www.usertrust.com]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[VeriSign Trust Network]
+country=US
+
+[www.xrampsecurity.com]
+country=US
+
+[certSIGN ROOT CA]
+country=RO
+
+[ePKI Root Certification Authority]
+country=TW
+
+[emSign PKI]
+country=US
+
+[emSign PKI]
+country=IN
+
+[emSign PKI]
+country=US
+
+[emSign PKI]
+country=IN
+
+[Certification Services Division]
+country=US
+
+[(c) 2007 thawte, Inc. - For authorized use only]
+country=US

+ 16 - 0
Libraries/LibTLS/Certificate.h

@@ -28,6 +28,7 @@
 
 #include <AK/ByteBuffer.h>
 #include <AK/Forward.h>
+#include <AK/Singleton.h>
 #include <AK/Types.h>
 #include <LibCrypto/BigInt/UnsignedBigInteger.h>
 #include <LibCrypto/PK/RSA.h>
@@ -77,6 +78,21 @@ struct Certificate {
     bool is_valid() const;
 };
 
+class DefaultRootCACertificates {
+public:
+    DefaultRootCACertificates();
+
+    const Vector<Certificate>& certificates() const { return m_ca_certificates; }
+
+    static DefaultRootCACertificates& the() { return s_the; }
+
+private:
+    static AK::Singleton<DefaultRootCACertificates> s_the;
+
+    Vector<Certificate> m_ca_certificates;
+};
+
 }
 
 using TLS::Certificate;
+using TLS::DefaultRootCACertificates;

+ 8 - 1
Libraries/LibTLS/ClientHandshake.cpp

@@ -274,7 +274,14 @@ void TLSv12::build_random(PacketBuilder& builder)
 
     m_context.premaster_key = ByteBuffer::copy(random_bytes, bytes);
 
-    const auto& certificate = m_context.certificates[0];
+    const auto& certificate_option = verify_chain_and_get_matching_certificate(m_context.SNI); // if the SNI is empty, we'll make a special case and match *a* leaf certificate.
+    if (!certificate_option.has_value()) {
+        dbg() << "certificate verification failed :(";
+        alert(AlertLevel::Critical, AlertDescription::BadCertificate);
+        return;
+    }
+
+    auto& certificate = m_context.certificates[certificate_option.value()];
 #ifdef TLS_DEBUG
     dbg() << "PreMaster secret";
     print_buffer(m_context.premaster_key);

+ 107 - 2
Libraries/LibTLS/TLSv12.cpp

@@ -25,6 +25,7 @@
  */
 
 #include <AK/Endian.h>
+#include <LibCore/ConfigFile.h>
 #include <LibCore/DateTime.h>
 #include <LibCore/Timer.h>
 #include <LibCrypto/ASN1/DER.h>
@@ -498,8 +499,10 @@ ssize_t TLSv12::handle_certificate(const ByteBuffer& buffer)
 
             auto certificate = parse_asn1(buffer.slice_view(res_cert, certificate_size_specific), false);
             if (certificate.has_value()) {
-                m_context.certificates.append(certificate.value());
-                valid_certificate = true;
+                if (certificate.value().is_valid()) {
+                    m_context.certificates.append(certificate.value());
+                    valid_certificate = true;
+                }
             }
             res_cert += certificate_size_specific;
         } while (remaining > 0);
@@ -705,6 +708,94 @@ void TLSv12::try_disambiguate_error() const
     }
 }
 
+void TLSv12::set_root_certificates(Vector<Certificate> certificates)
+{
+    if (!m_context.root_ceritificates.is_empty())
+        dbg() << "TLS warn: resetting root certificates!";
+
+    for (auto& cert : certificates) {
+        if (!cert.is_valid())
+            dbg() << "Certificate for " << cert.subject << " by " << cert.issuer_subject << " is invalid, things may or may not work!";
+        // FIXME: Figure out what we should do when our root certs are invalid.
+    }
+    m_context.root_ceritificates = move(certificates);
+}
+
+bool Context::verify_chain() const
+{
+    const Vector<Certificate>* local_chain = nullptr;
+    if (is_server) {
+        dbg() << "Unsupported: Server mode";
+        TODO();
+    } else {
+        local_chain = &certificates;
+    }
+
+    // FIXME: Actually verify the signature, instead of just checking the name.
+    HashMap<String, String> chain;
+    HashTable<String> roots;
+    // First, walk the root certs.
+    for (auto& cert : root_ceritificates) {
+        roots.set(cert.subject);
+        chain.set(cert.subject, cert.issuer_subject);
+    }
+
+    // Then, walk the local certs.
+    for (auto& cert : *local_chain) {
+        auto& issuer_unique_name = cert.issuer_unit.is_empty() ? cert.issuer_subject : cert.issuer_unit;
+        chain.set(cert.subject, issuer_unique_name);
+    }
+
+    // Then verify the chain.
+    for (auto& it : chain) {
+        if (it.key == it.value) { // Allow self-signed certificates.
+            if (!roots.contains(it.key))
+                dbg() << "Self-signed warning: Certificate for " << it.key << " is self-signed";
+            continue;
+        }
+
+        auto ref = chain.get(it.value);
+        if (!ref.has_value()) {
+            dbg() << "Certificate for " << it.key << " is not signed by anyone we trust (" << it.value << ")";
+            return false;
+        }
+
+        if (ref.value() == it.key) // Allow (but warn about) mutually recursively signed cert A <-> B.
+            dbg() << "Co-dependency warning: Certificate for " << ref.value() << " is issued by " << it.key << ", which itself is issued by " << ref.value();
+    }
+
+    return true;
+}
+
+static bool wildcard_matches(const StringView& host, const StringView& subject)
+{
+    if (host.matches(subject))
+        return true;
+
+    if (subject.starts_with("*."))
+        return wildcard_matches(host, subject.substring_view(2));
+
+    return false;
+}
+
+Optional<size_t> TLSv12::verify_chain_and_get_matching_certificate(const StringView& host) const
+{
+    if (m_context.certificates.is_empty() || !m_context.verify_chain())
+        return {};
+
+    if (host.is_empty())
+        return 0;
+
+    for (size_t i = 0; i < m_context.certificates.size(); ++i) {
+        auto& cert = m_context.certificates[i];
+        // FIXME: Also check against SAN (oid 2.5.29.17).
+        if (wildcard_matches(host, cert.subject))
+            return i;
+    }
+
+    return {};
+}
+
 TLSv12::TLSv12(Core::Object* parent, Version version)
     : Core::Socket(Core::Socket::Type::TCP, parent)
 {
@@ -751,4 +842,18 @@ bool TLSv12::add_client_key(const ByteBuffer& certificate_pem_buffer, const Byte
     return add_client_key(certificate);
 }
 
+AK::Singleton<DefaultRootCACertificates> DefaultRootCACertificates::s_the;
+DefaultRootCACertificates::DefaultRootCACertificates()
+{
+    // FIXME: This might not be the best format, find a better way to represent CA certificates.
+    auto config = Core::ConfigFile::get_for_system("ca_certs");
+    for (auto& entity : config->groups()) {
+        Certificate cert;
+        cert.subject = entity;
+        cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity);
+        cert.country = config->read_entry(entity, "country");
+        m_ca_certificates.append(move(cert));
+    }
+}
+
 }

+ 4 - 0
Libraries/LibTLS/TLSv12.h

@@ -279,6 +279,8 @@ public:
     bool load_certificates(const ByteBuffer& pem_buffer);
     bool load_private_key(const ByteBuffer& pem_buffer);
 
+    void set_root_certificates(Vector<Certificate>);
+
     bool add_client_key(const ByteBuffer& certificate_pem_buffer, const ByteBuffer& key_pem_buffer);
     bool add_client_key(Certificate certificate)
     {
@@ -429,6 +431,8 @@ private:
 
     bool compute_master_secret(size_t length);
 
+    Optional<size_t> verify_chain_and_get_matching_certificate(const StringView& host) const;
+
     void try_disambiguate_error() const;
 
     Context m_context;

+ 1 - 1
Meta/Lagom/CMakeLists.txt

@@ -94,7 +94,7 @@ if (BUILD_LAGOM)
     target_link_libraries(test-crypto_lagom stdc++)
     add_test(
         NAME Crypto
-        COMMAND test-crypto_lagom test -t -s google.com
+        COMMAND test-crypto_lagom test -t -s google.com --ca-certs-file ../../Base/etc/ca_certs.ini
         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     )
 

+ 31 - 0
Userland/test-crypto.cpp

@@ -26,6 +26,7 @@
 
 #include <AK/Random.h>
 #include <LibCore/ArgsParser.h>
+#include <LibCore/ConfigFile.h>
 #include <LibCore/EventLoop.h>
 #include <LibCore/File.h>
 #include <LibCrypto/Authentication/HMAC.h>
@@ -48,6 +49,7 @@ static const char* secret_key = "WellHelloFreinds";
 static const char* suite = nullptr;
 static const char* filename = nullptr;
 static const char* server = nullptr;
+static const char* ca_certs_file = "/etc/ca_certs.ini";
 static int key_bits = 128;
 static bool binary = false;
 static bool interactive = false;
@@ -67,6 +69,8 @@ constexpr const char* DEFAULT_HASH_SUITE { "SHA256" };
 constexpr const char* DEFAULT_CIPHER_SUITE { "AES_CBC" };
 constexpr const char* DEFAULT_SERVER { "www.google.com" };
 
+static Vector<Certificate> s_root_ca_certificates;
+
 // listAllTests
 // Cipher
 static int aes_cbc_tests();
@@ -165,6 +169,7 @@ static void tls(const char* message, size_t len)
     static ByteBuffer write {};
     if (!tls) {
         tls = TLS::TLSv12::construct(nullptr);
+        tls->set_root_certificates(s_root_ca_certificates);
         tls->connect(server ?: DEFAULT_SERVER, port);
         tls->on_tls_ready_to_read = [](auto& tls) {
             auto buffer = tls.read();
@@ -313,6 +318,7 @@ auto main(int argc, char** argv) -> int
     parser.add_option(suite, "Set the suite used", "suite-name", 'n', "suite name");
     parser.add_option(server, "Set the server to talk to (only for `tls')", "server-address", 's', "server-address");
     parser.add_option(port, "Set the port to talk to (only for `tls')", "port", 'p', "port");
+    parser.add_option(ca_certs_file, "INI file to read root CA certificates from (only for `tls')", "ca-certs-file", 0, "file");
     parser.add_option(in_ci, "CI Test mode", "ci-mode", 'c');
     parser.parse(argc, argv);
 
@@ -413,6 +419,18 @@ auto main(int argc, char** argv) -> int
         return bigint_tests();
     }
     if (mode_sv == "tls") {
+        if (!Core::File::exists(ca_certs_file)) {
+            warnln("Nonexistent CA certs file '{}'", ca_certs_file);
+            return 1;
+        }
+        auto config = Core::ConfigFile::open(ca_certs_file);
+        for (auto& entity : config->groups()) {
+            Certificate cert;
+            cert.subject = entity;
+            cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity);
+            cert.country = config->read_entry(entity, "country");
+            s_root_ca_certificates.append(move(cert));
+        }
         if (run_tests)
             return tls_tests();
         return run(tls);
@@ -440,6 +458,18 @@ auto main(int argc, char** argv) -> int
 
         if (!in_ci) {
             // Do not run these in CI to avoid tests with variables outside our control.
+            if (!Core::File::exists(ca_certs_file)) {
+                warnln("Nonexistent CA certs file '{}'", ca_certs_file);
+                return 1;
+            }
+            auto config = Core::ConfigFile::open(ca_certs_file);
+            for (auto& entity : config->groups()) {
+                Certificate cert;
+                cert.subject = entity;
+                cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity);
+                cert.country = config->read_entry(entity, "country");
+                s_root_ca_certificates.append(move(cert));
+            }
             tls_tests();
         }
 
@@ -1723,6 +1753,7 @@ static void tls_test_client_hello()
     I_TEST((TLS | Connect and Data Transfer));
     Core::EventLoop loop;
     RefPtr<TLS::TLSv12> tls = TLS::TLSv12::construct(nullptr);
+    tls->set_root_certificates(s_root_ca_certificates);
     bool sent_request = false;
     ByteBuffer contents = ByteBuffer::create_uninitialized(0);
     tls->on_tls_ready_to_write = [&](TLS::TLSv12& tls) {