diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-10-30 11:56:31 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-10-30 23:42:03 +0100 |
commit | 37c089fb7b31d37be302384de4313deea6191c75 (patch) | |
tree | 78072b72a49312248e1165298bdbbaccea4f9520 | |
parent | 34f8d55100488d02ad28779bf6cffde4831d184e (diff) | |
download | serenity-37c089fb7b31d37be302384de4313deea6191c75.zip |
LibTLS: (Almost) verify certificate chain against root CA certificates
Also adds a very primitive systemwide ca_certs.ini file.
-rw-r--r-- | Base/etc/ca_certs.ini | 465 | ||||
-rw-r--r-- | Libraries/LibTLS/Certificate.h | 16 | ||||
-rw-r--r-- | Libraries/LibTLS/ClientHandshake.cpp | 9 | ||||
-rw-r--r-- | Libraries/LibTLS/TLSv12.cpp | 109 | ||||
-rw-r--r-- | Libraries/LibTLS/TLSv12.h | 4 | ||||
-rw-r--r-- | Meta/Lagom/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Userland/test-crypto.cpp | 31 |
7 files changed, 632 insertions, 4 deletions
diff --git a/Base/etc/ca_certs.ini b/Base/etc/ca_certs.ini new file mode 100644 index 0000000000..89c476f906 --- /dev/null +++ b/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 diff --git a/Libraries/LibTLS/Certificate.h b/Libraries/LibTLS/Certificate.h index e5f67942a9..2f2de40d87 100644 --- a/Libraries/LibTLS/Certificate.h +++ b/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; diff --git a/Libraries/LibTLS/ClientHandshake.cpp b/Libraries/LibTLS/ClientHandshake.cpp index 214fce237a..6fb48bf565 100644 --- a/Libraries/LibTLS/ClientHandshake.cpp +++ b/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); diff --git a/Libraries/LibTLS/TLSv12.cpp b/Libraries/LibTLS/TLSv12.cpp index fd78dd74ad..cf823f58c8 100644 --- a/Libraries/LibTLS/TLSv12.cpp +++ b/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)); + } +} + } diff --git a/Libraries/LibTLS/TLSv12.h b/Libraries/LibTLS/TLSv12.h index 08abdc0832..3e2788dd61 100644 --- a/Libraries/LibTLS/TLSv12.h +++ b/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; diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index e74e4e9e85..f341ab722f 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/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} ) diff --git a/Userland/test-crypto.cpp b/Userland/test-crypto.cpp index 21ce755b76..a473ee3e30 100644 --- a/Userland/test-crypto.cpp +++ b/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) { |