diff options
| -rw-r--r-- | CMake/FindGCRYPT.cmake | 25 | ||||
| -rw-r--r-- | CMake/config.h.in | 3 | ||||
| -rw-r--r-- | CMakeLists.txt | 6 | ||||
| -rw-r--r-- | COPYING | 21 | ||||
| -rw-r--r-- | apt-pkg/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | apt-pkg/contrib/gpgv.cc | 4 | ||||
| -rw-r--r-- | apt-pkg/contrib/hashes.cc | 172 | ||||
| -rw-r--r-- | cmdline/apt-helper.cc | 51 | ||||
| -rw-r--r-- | debian/control | 3 | ||||
| -rw-r--r-- | debian/tests/control | 3 | ||||
| -rw-r--r-- | methods/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | methods/connect.cc | 386 | ||||
| -rw-r--r-- | methods/http.cc | 15 |
13 files changed, 374 insertions, 327 deletions
diff --git a/CMake/FindGCRYPT.cmake b/CMake/FindGCRYPT.cmake deleted file mode 100644 index 56bfc9fef..000000000 --- a/CMake/FindGCRYPT.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# - Try to find GCRYPT -# Once done, this will define -# -# GCRYPT_FOUND - system has GCRYPT -# GCRYPT_INCLUDE_DIRS - the GCRYPT include directories -# GCRYPT_LIBRARIES - the GCRYPT library -find_package(PkgConfig) - -pkg_check_modules(GCRYPT_PKGCONF libgcrypt) - -find_path(GCRYPT_INCLUDE_DIRS - NAMES gcrypt.h - PATHS ${GCRYPT_PKGCONF_INCLUDE_DIRS} -) - - -find_library(GCRYPT_LIBRARIES - NAMES gcrypt - PATHS ${GCRYPT_PKGCONF_LIBRARY_DIRS} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GCRYPT DEFAULT_MSG GCRYPT_INCLUDE_DIRS GCRYPT_LIBRARIES) - -mark_as_advanced(GCRYPT_INCLUDE_DIRS GCRYPT_LIBRARIES) diff --git a/CMake/config.h.in b/CMake/config.h.in index 8346a5e9e..ced74d8d8 100644 --- a/CMake/config.h.in +++ b/CMake/config.h.in @@ -5,9 +5,6 @@ /* Define if we have the timegm() function */ #cmakedefine HAVE_TIMEGM -/* Define if we have the gnutls library for TLS */ -#cmakedefine HAVE_GNUTLS - /* Define if we have the zlib library for gzip */ #cmakedefine HAVE_ZLIB diff --git a/CMakeLists.txt b/CMakeLists.txt index c6977675e..af2ec3a86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,10 +92,7 @@ if (BERKELEY_FOUND) set(HAVE_BDB 1) endif() -find_package(GnuTLS) -if (GNUTLS_FOUND) - set(HAVE_GNUTLS 1) -endif() +find_package(OpenSSL REQUIRED) # (De)Compressor libraries find_package(ZLIB REQUIRED) @@ -141,7 +138,6 @@ if (SECCOMP_FOUND) set(HAVE_SECCOMP 1) endif() -find_package(GCRYPT REQUIRED) find_package(XXHASH REQUIRED) # Mount()ing and stat()ing and friends @@ -52,6 +52,10 @@ Copyright: 1997-1999 Jason Gunthorpe and others 2014 Anthony Towns License: GPL-2+ +Files: methods/connect.c +Copyright: Copyright (c) 1996 - 2023, Daniel Stenberg, <daniel@haxx.se>, and many contributors +License: GPL-2+ and curl + Files: methods/rsh.cc Copyright: 2000 Ben Collins <bcollins@debian.org> License: GPL-2 @@ -153,3 +157,20 @@ License: GPL-2+ Comment: On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +License: curl + Permission to use, copy, modify, and distribute this software for any purpose + with or without fee is hereby granted, provided that the above copyright + notice and this permission notice appear in all copies. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + OR OTHER DEALINGS IN THE SOFTWARE. + . + Except as contained in this notice, the name of a copyright holder shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization of the copyright holder. diff --git a/apt-pkg/CMakeLists.txt b/apt-pkg/CMakeLists.txt index 63052faad..4c036cc67 100644 --- a/apt-pkg/CMakeLists.txt +++ b/apt-pkg/CMakeLists.txt @@ -56,7 +56,7 @@ target_include_directories(apt-pkg $<$<BOOL:${UDEV_FOUND}>:${UDEV_INCLUDE_DIRS}> $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_INCLUDE_DIRS}> ${ICONV_INCLUDE_DIRS} - $<$<BOOL:${GCRYPT_FOUND}>:${GCRYPT_INCLUDE_DIRS}> + ${OPENSSL_INCLUDE_DIRS} $<$<BOOL:${XXHASH_FOUND}>:${XXHASH_INCLUDE_DIRS}> ) @@ -71,7 +71,7 @@ target_link_libraries(apt-pkg $<$<BOOL:${UDEV_FOUND}>:${UDEV_LIBRARIES}> $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_LIBRARIES}> ${ICONV_LIBRARIES} - $<$<BOOL:${GCRYPT_FOUND}>:${GCRYPT_LIBRARIES}> + OpenSSL::Crypto $<$<BOOL:${XXHASH_FOUND}>:${XXHASH_LIBRARIES}> ) set_target_properties(apt-pkg PROPERTIES VERSION ${MAJOR}.${MINOR}) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 565fb7de5..7b0a0d240 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -255,12 +255,8 @@ std::pair<std::string, std::forward_list<std::string>> APT::Internal::FindGPGV(b // Prefer absolute path "/usr/bin/gpgv-sq", "/usr/bin/gpgv", - "/usr/bin/gpgv2", - "/usr/bin/gpgv1", "gpgv-sq", "gpgv", - "gpgv2", - "gpgv1", }; for (auto gpgv : gpgvVariants) if (CheckGPGV(checkedCommands, gpgv, Debug)) diff --git a/apt-pkg/contrib/hashes.cc b/apt-pkg/contrib/hashes.cc index 06bfd003e..c7a4ab0e7 100644 --- a/apt-pkg/contrib/hashes.cc +++ b/apt-pkg/contrib/hashes.cc @@ -26,24 +26,13 @@ #include <cstddef> #include <cstdlib> #include <iostream> +#include <optional> #include <string> #include <unistd.h> -#include <gcrypt.h> +#include <openssl/evp.h> /*}}}*/ -static const constexpr struct HashAlgo -{ - const char *name; - int gcryAlgo; - Hashes::SupportedHashes ourAlgo; -} Algorithms[] = { - {"MD5Sum", GCRY_MD_MD5, Hashes::MD5SUM}, - {"SHA1", GCRY_MD_SHA1, Hashes::SHA1SUM}, - {"SHA256", GCRY_MD_SHA256, Hashes::SHA256SUM}, - {"SHA512", GCRY_MD_SHA512, Hashes::SHA512SUM}, -}; - const char * HashString::_SupportedHashes[] = { "SHA512", "SHA256", "SHA1", "MD5Sum", "Checksum-FileSize", NULL @@ -311,58 +300,114 @@ bool HashStringList::operator!=(HashStringList const &other) const return !(*this == other); } /*}}}*/ +static APT_PURE std::string HexDigest(std::basic_string_view<unsigned char> const &Sum) +{ + char Conv[16] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f'}; + std::string Result(Sum.size() * 2, 0); + + // Convert each char into two letters + size_t J = 0; + size_t I = 0; + for (; I != (Sum.size()) * 2; J++, I += 2) + { + Result[I] = Conv[Sum[J] >> 4]; + Result[I + 1] = Conv[Sum[J] & 0xF]; + } + return Result; +}; // PrivateHashes /*{{{*/ -class PrivateHashes { -public: - unsigned long long FileSize; - gcry_md_hd_t hd; +class PrivateHashes +{ + public: + unsigned long long FileSize{0}; - void maybeInit() - { + private: + std::array<EVP_MD_CTX *, 4> contexts{}; - // Yikes, we got to initialize libgcrypt, or we get warnings. But we - // abstract away libgcrypt in Hashes from our users - they are not - // supposed to know what the hashing backend is, so we can't force - // them to init themselves as libgcrypt folks want us to. So this - // only leaves us with this option... - if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + public: + struct HashAlgo + { + size_t index; + const char *name; + const EVP_MD *(*evpLink)(void); + Hashes::SupportedHashes ourAlgo; + }; + + static constexpr std::array<HashAlgo, 4> Algorithms{ + HashAlgo{0, "MD5Sum", EVP_md5, Hashes::MD5SUM}, + HashAlgo{1, "SHA1", EVP_sha1, Hashes::SHA1SUM}, + HashAlgo{2, "SHA256", EVP_sha256, Hashes::SHA256SUM}, + HashAlgo{3, "SHA512", EVP_sha512, Hashes::SHA512SUM}, + }; + + bool Write(unsigned char const *Data, size_t Size) + { + for (auto &context : contexts) { - if (!gcry_check_version(nullptr)) - { - fprintf(stderr, "libgcrypt is too old (need %s, have %s)\n", - "nullptr", gcry_check_version(NULL)); - exit(2); - } - - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + if (context) + EVP_DigestUpdate(context, Data, Size); } + return true; + } + + std::string HexDigest(HashAlgo const &algo) + { + auto Size = EVP_MD_size(algo.evpLink()); + unsigned char Sum[Size]; + + // We need to work on a copy, as we update the hash after creating a digest... + auto tmpContext = EVP_MD_CTX_create(); + EVP_MD_CTX_copy(tmpContext, contexts[algo.index]); + EVP_DigestFinal_ex(tmpContext, Sum, nullptr); + EVP_MD_CTX_destroy(tmpContext); + + return ::HexDigest(std::basic_string_view<unsigned char>(Sum, Size)); + } + + bool Enable(HashAlgo const &algo) + { + contexts[algo.index] = EVP_MD_CTX_new(); + if (contexts[algo.index] == nullptr) + return false; + if (EVP_DigestInit_ex(contexts[algo.index], algo.evpLink(), NULL)) + return true; + EVP_MD_CTX_destroy(contexts[algo.index]); + contexts[algo.index] = nullptr; + return false; + } + bool IsEnabled(HashAlgo const &algo) + { + return contexts[algo.index] != nullptr; + } + + explicit PrivateHashes() {} + ~PrivateHashes() + { + for (auto ctx : contexts) + if (ctx != nullptr) + EVP_MD_CTX_free(ctx); } - explicit PrivateHashes(unsigned int const CalcHashes) : FileSize(0) + explicit PrivateHashes(unsigned int const CalcHashes) : PrivateHashes() { - maybeInit(); - gcry_md_open(&hd, 0, 0); for (auto & Algo : Algorithms) { if ((CalcHashes & Algo.ourAlgo) == Algo.ourAlgo) - gcry_md_enable(hd, Algo.gcryAlgo); + Enable(Algo); } } - explicit PrivateHashes(HashStringList const &Hashes) : FileSize(0) { - maybeInit(); - gcry_md_open(&hd, 0, 0); + explicit PrivateHashes(HashStringList const &Hashes) : PrivateHashes() + { for (auto & Algo : Algorithms) { if (not Hashes.usable() || Hashes.find(Algo.name) != NULL) - gcry_md_enable(hd, Algo.gcryAlgo); + Enable(Algo); } } - ~PrivateHashes() - { - gcry_md_close(hd); - } }; /*}}}*/ // Hashes::Add* - Add the contents of data or FD /*{{{*/ @@ -370,7 +415,8 @@ bool Hashes::Add(const unsigned char * const Data, unsigned long long const Size { if (Size != 0) { - gcry_md_write(d->hd, Data, Size); + if (not d->Write(Data, Size)) + return false; d->FileSize += Size; } return true; @@ -420,36 +466,12 @@ bool Hashes::AddFD(FileFd &Fd,unsigned long long Size) } /*}}}*/ -static APT_PURE std::string HexDigest(gcry_md_hd_t hd, int algo) -{ - char Conv[16] = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f'}; - - auto Size = gcry_md_get_algo_dlen(algo); - assert(Size <= 512/8); - char Result[((Size)*2) + 1]; - Result[(Size)*2] = 0; - - auto Sum = gcry_md_read(hd, algo); - - // Convert each char into two letters - size_t J = 0; - size_t I = 0; - for (; I != (Size)*2; J++, I += 2) - { - Result[I] = Conv[Sum[J] >> 4]; - Result[I + 1] = Conv[Sum[J] & 0xF]; - } - return std::string(Result); -}; - HashStringList Hashes::GetHashStringList() { HashStringList hashes; - for (auto & Algo : Algorithms) - if (gcry_md_is_enabled(d->hd, Algo.gcryAlgo)) - hashes.push_back(HashString(Algo.name, HexDigest(d->hd, Algo.gcryAlgo))); + for (auto &Algo : d->Algorithms) + if (d->IsEnabled(Algo)) + hashes.push_back(HashString(Algo.name, d->HexDigest(Algo))); hashes.FileSize(d->FileSize); return hashes; @@ -457,9 +479,9 @@ HashStringList Hashes::GetHashStringList() HashString Hashes::GetHashString(SupportedHashes hash) { - for (auto & Algo : Algorithms) + for (auto &Algo : d->Algorithms) if (hash == Algo.ourAlgo) - return HashString(Algo.name, HexDigest(d->hd, Algo.gcryAlgo)); + return HashString(Algo.name, d->HexDigest(Algo)); abort(); } diff --git a/cmdline/apt-helper.cc b/cmdline/apt-helper.cc index f48cb293f..4404259d8 100644 --- a/cmdline/apt-helper.cc +++ b/cmdline/apt-helper.cc @@ -201,7 +201,37 @@ static bool DoCatFile(CommandLine &CmdL) /*{{{*/ return true; } /*}}}*/ +static bool DoHashFile(CommandLine &CmdL) /*{{{*/ +{ + FileFd fd; + + if (fd.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + return false; + for (size_t i = 1; CmdL.FileList[i] != NULL; ++i) + { + std::string const name = CmdL.FileList[i]; + Hashes hashes; + + if (name != "-") + { + if (fd.Open(name, FileFd::ReadOnly, FileFd::Extension) == false) + return false; + } + else + { + if (fd.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + return false; + } + if (hashes.AddFD(fd) == false) + return false; + for (auto hs : hashes.GetHashStringList()) + std::cout << hs.toStr() << std::endl; + std::cout << std::endl; + } + return true; +} + /*}}}*/ static pid_t ExecuteProcess(const char *Args[]) /*{{{*/ { pid_t pid = ExecFork(); @@ -310,16 +340,17 @@ static bool ShowHelp(CommandLine &) /*{{{*/ static std::vector<aptDispatchWithHelp> GetCommands() /*{{{*/ { return { - {"download-file", &DoDownloadFile, _("download the given uri to the target-path")}, - {"srv-lookup", &DoSrvLookup, _("lookup a SRV record (e.g. _http._tcp.ftp.debian.org)")}, - {"cat-file", &DoCatFile, _("concatenate files, with automatic decompression")}, - {"auto-detect-proxy", &DoAutoDetectProxy, _("detect proxy using apt.conf")}, - {"wait-online", &DoWaitOnline, _("wait for system to be online")}, - {"drop-privs", &DropPrivsAndRun, _("drop privileges before running given command")}, - {"analyze-pattern", &AnalyzePattern, _("analyse a pattern")}, - {"analyse-pattern", &AnalyzePattern, nullptr}, - {"quote-string", &DoQuoteString, nullptr}, - {nullptr, nullptr, nullptr}}; + {"download-file", &DoDownloadFile, _("download the given uri to the target-path")}, + {"srv-lookup", &DoSrvLookup, _("lookup a SRV record (e.g. _http._tcp.ftp.debian.org)")}, + {"cat-file", &DoCatFile, _("concatenate files, with automatic decompression")}, + {"hash-file", &DoHashFile, _("hash file")}, + {"auto-detect-proxy", &DoAutoDetectProxy, _("detect proxy using apt.conf")}, + {"wait-online", &DoWaitOnline, _("wait for system to be online")}, + {"drop-privs", &DropPrivsAndRun, _("drop privileges before running given command")}, + {"analyze-pattern", &AnalyzePattern, _("analyse a pattern")}, + {"analyse-pattern", &AnalyzePattern, nullptr}, + {"quote-string", &DoQuoteString, nullptr}, + {nullptr, nullptr, nullptr}}; } /*}}}*/ int main(int argc,const char *argv[]) /*{{{*/ diff --git a/debian/control b/debian/control index 588539a02..50297362e 100644 --- a/debian/control +++ b/debian/control @@ -17,8 +17,7 @@ Build-Depends: dpkg-dev (>= 1.22.5) <!pkg.apt.ci>, googletest <!nocheck> | libgtest-dev <!nocheck>, libbz2-dev, libdb-dev, - libgnutls28-dev (>= 3.4.6), - libgcrypt20-dev, + libssl-dev, liblz4-dev (>= 0.0~r126), liblzma-dev, libseccomp-dev (>= 2.4.2) [amd64 arm64 armel armhf i386 mips mips64el mipsel ppc64el s390x hppa powerpc powerpcspe ppc64 x32], diff --git a/debian/tests/control b/debian/tests/control index 3aef98710..428bed456 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -5,8 +5,7 @@ Depends: libapt-pkg-dev, pkg-config, g++ Tests: run-tests Restrictions: allow-stderr Depends: @, @builddeps@, dpkg (>= 1.20.8), expect, fakeroot, wget, stunnel4, lsof, db-util, - gnupg (>= 2) | gnupg2, gnupg1 | gnupg (<< 2), - gpgv (>= 2) | gpgv2, gpgv1 | gpgv (<< 2), + gpgv, gpgv-sq, sq (>= 0.40), moreutils, diff --git a/methods/CMakeLists.txt b/methods/CMakeLists.txt index 548d867dc..beb08a81d 100644 --- a/methods/CMakeLists.txt +++ b/methods/CMakeLists.txt @@ -13,14 +13,12 @@ add_executable(http http.cc basehttp.cc $<TARGET_OBJECTS:connectlib>) add_executable(mirror mirror.cc) add_executable(rred rred.cc) -if (HAVE_GNUTLS) - target_compile_definitions(connectlib PRIVATE ${GNUTLS_DEFINITIONS}) - target_include_directories(connectlib PRIVATE ${GNUTLS_INCLUDE_DIR}) -endif() +target_compile_definitions(connectlib PRIVATE ${OPENSSL_DEFINITIONS}) +target_include_directories(connectlib PRIVATE ${OPENSSL_INCLUDE_DIR}) target_include_directories(http PRIVATE $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_INCLUDE_DIRS}>) # Additional libraries to link against for networked stuff -target_link_libraries(http $<$<BOOL:${GNUTLS_FOUND}>:${GNUTLS_LIBRARIES}> $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_LIBRARIES}>) +target_link_libraries(http OpenSSL::SSL $<$<BOOL:${SYSTEMD_FOUND}>:${SYSTEMD_LIBRARIES}>) target_link_libraries(rred apt-private) diff --git a/methods/connect.cc b/methods/connect.cc index 12847c594..b00e73eb7 100644 --- a/methods/connect.cc +++ b/methods/connect.cc @@ -1,12 +1,14 @@ // -*- mode: cpp; mode: fold -*- +// SPDX-License-Identifier: GPL-2.0+ and curl // Description /*{{{*/ /* ###################################################################### Connect - Replacement connect call This was originally authored by Jason Gunthorpe <jgg@debian.org> - and is placed in the Public Domain, do with it what you will. - + and is placed in the Public Domain, do with it what you will. It + is now GPL-2.0+. See COPYING for details. + ##################################################################### */ /*}}}*/ // Include Files /*{{{*/ @@ -19,11 +21,10 @@ #include <apt-pkg/srvrec.h> #include <apt-pkg/strutl.h> -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#endif +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <cassert> #include <cerrno> #include <cstdio> #include <cstring> @@ -801,229 +802,242 @@ ResultState UnwrapSocks(std::string Host, int Port, URI Proxy, std::unique_ptr<M return ResultState::SUCCESSFUL; } -#ifdef HAVE_GNUTLS /*}}}*/ // UnwrapTLS - Handle TLS connections /*{{{*/ // --------------------------------------------------------------------- /* Performs a TLS handshake on the socket */ +#define null_error(...) (_error->Error(__VA_ARGS__), nullptr) +#define ssl_strerr() ERR_error_string(ERR_get_error(), nullptr) struct TlsFd final : public MethodFd { - std::unique_ptr<MethodFd> UnderlyingFd; - gnutls_session_t session; - gnutls_certificate_credentials_t credentials; - std::string hostname; - unsigned long Timeout; + std::unique_ptr<MethodFd> UnderlyingFd{}; + + SSL *ssl{}; - int Fd() APT_OVERRIDE { return UnderlyingFd->Fd(); } + std::string hostname{}; + unsigned long Timeout{}; + bool broken{false}; - ssize_t Read(void *buf, size_t count) APT_OVERRIDE + int Fd() override { return UnderlyingFd ? UnderlyingFd->Fd() : -1; } + + ssize_t Read(void *buf, size_t count) override { - return HandleError(gnutls_record_recv(session, buf, count)); + assert(ssl); + return HandleError(SSL_read(ssl, buf, count)); } - ssize_t Write(void *buf, size_t count) APT_OVERRIDE + ssize_t Write(void *buf, size_t count) override { - return HandleError(gnutls_record_send(session, buf, count)); + assert(ssl); + return HandleError(SSL_write(ssl, buf, count)); } - ssize_t DoTLSHandshake() + ssize_t HandleError(ssize_t r) { - int err; - // Do the handshake. Our socket is non-blocking, so we need to call WaitFd() - // accordingly. - do - { - err = gnutls_handshake(session); - if ((err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN) && - WaitFd(this->Fd(), gnutls_record_get_direction(session) == 1, Timeout) == false) - { - _error->Errno("select", "Could not wait for server fd"); - return err; - } - } while (err < 0 && gnutls_error_is_fatal(err) == 0); - - if (err < 0) + assert(ssl); + if (r > 0) + return r; + + auto err = SSL_get_error(ssl, r); + switch (err) { - // Print reason why validation failed. - if (err == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) - { - gnutls_datum_t txt; - auto type = gnutls_certificate_type_get(session); - auto status = gnutls_session_get_verify_cert_status(session); - if (gnutls_certificate_verification_status_print(status, type, &txt, 0) == 0) - { - _error->Error("Certificate verification failed: %s", txt.data); - } - gnutls_free(txt.data); - } - _error->Error("Could not handshake: %s", gnutls_strerror(err)); + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + break; + case SSL_ERROR_ZERO_RETURN: + // Remote host has closed connection + errno = 0; + break; + case SSL_ERROR_SYSCALL: + broken = true; + break; + case SSL_ERROR_SSL: + broken = true; + errno = EIO; + _error->Error("OpenSSL error: %s\n", ssl_strerr()); + break; } - return err; + return r; } - template <typename T> - T HandleError(T err) + int Close() override { - // Server may request re-handshake if client certificates need to be provided - // based on resource requested - if (err == GNUTLS_E_REHANDSHAKE) + int res = 0; // 0 or 1 are success + int lower = 0; // 0 is success + + if (ssl) { - int rc = DoTLSHandshake(); - // Only reset err if DoTLSHandshake() fails. - // Otherwise, we want to follow the original error path and set errno to EAGAIN - // so that the request is retried. - if (rc < 0) - err = rc; + if (not broken && res) + res = SSL_shutdown(ssl); + if (res < 0) + HandleError(res); + SSL_free(ssl); } + ssl = nullptr; - if (err < 0 && gnutls_error_is_fatal(err)) - errno = EIO; - else if (err < 0) - errno = EAGAIN; - else - errno = 0; - return err; - } + if (UnderlyingFd) + lower = UnderlyingFd->Close(); + UnderlyingFd = nullptr; - int Close() APT_OVERRIDE - { - auto err = HandleError(gnutls_bye(session, GNUTLS_SHUT_RDWR)); - auto lower = UnderlyingFd->Close(); - return err < 0 ? HandleError(err) : lower; + // Return -1 on failure, 0 on success + return res < 0 || lower < 0 ? -1 : 0; } - bool HasPending() APT_OVERRIDE + bool HasPending() override { - return gnutls_record_check_pending(session) > 0; + assert(ssl); + // SSL_has_pending() can return 1 even if there are no actual bytes to read + // post decoding, so we need to SSL_peek() too to see if there are actual + // bytes as otherwise we end up busy looping (tested by DE -> AU https connection) + char buf; + return SSL_has_pending(ssl) && SSL_peek(ssl, &buf, 1) > 0; } }; -ResultState UnwrapTLS(std::string const &Host, std::unique_ptr<MethodFd> &Fd, - unsigned long const Timeout, aptMethod * const Owner, - aptConfigWrapperForMethods const * const OwnerConf) +static BIO_METHOD *NewBioMethod() { - if (_config->FindB("Acquire::AllowTLS", true) == false) + BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL APT BIO method"); + if (not m) { - _error->Error("TLS support has been disabled: Acquire::AllowTLS is false."); - return ResultState::FATAL_ERROR; + _error->Error("SSL connection failed: %s - %s", ssl_strerr(), strerror(errno)); + return nullptr; } - int err; - TlsFd *tlsFd = new TlsFd(); + BIO_meth_set_ctrl(m, [](BIO *, int, long, void *) -> long + { return 1; }); + BIO_meth_set_write(m, [](BIO *bio, const char *buf, int size) -> int + { + auto p = BIO_get_data(bio); + auto res = reinterpret_cast<MethodFd *>(p)->Write((void*)buf, size); + if (errno == EAGAIN) + BIO_set_retry_write(bio); + return res; }); + BIO_meth_set_read(m, [](BIO *bio, char *buf, int size) -> int + { + auto p = BIO_get_data(bio); + auto res = reinterpret_cast<MethodFd *>(p)->Read(buf, size); + if (errno == EAGAIN) + BIO_set_retry_read(bio); + return res; }); + + return m; +} - tlsFd->hostname = Host; - tlsFd->UnderlyingFd = MethodFd::FromFd(-1); // For now - tlsFd->Timeout = Timeout; +static SSL_CTX *GetContextForHost(std::string const &host, aptConfigWrapperForMethods const *const OwnerConf) +{ + static std::string lastHost; + static SSL_CTX *ctx; + auto Debug = OwnerConf->DebugEnabled(); - if ((err = gnutls_init(&tlsFd->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK)) < 0) + if (lastHost == host) { - _error->Error("Internal error: could not allocate credentials: %s", gnutls_strerror(err)); - return ResultState::FATAL_ERROR; + if (Debug) + std::clog << "Reusing context for " << host << std::endl; + return ctx; } + if (Debug) + std::clog << "Creating context for " << host << std::endl; - FdFd *fdfd = dynamic_cast<FdFd *>(Fd.get()); - if (fdfd != nullptr) - { - gnutls_transport_set_int(tlsFd->session, fdfd->fd); - } - else - { - gnutls_transport_set_ptr(tlsFd->session, Fd.get()); - gnutls_transport_set_pull_function(tlsFd->session, - [](gnutls_transport_ptr_t p, void *buf, size_t size) -> ssize_t { - return reinterpret_cast<MethodFd *>(p)->Read(buf, size); - }); - gnutls_transport_set_push_function(tlsFd->session, - [](gnutls_transport_ptr_t p, const void *buf, size_t size) -> ssize_t { - return reinterpret_cast<MethodFd *>(p)->Write((void *)buf, size); - }); - } + // Check unsupported options first, maybe we reuse the host context later, who knows + if (not OwnerConf->ConfigFind("IssuerCert", "").empty()) + return null_error("The option '%s' is not supported anymore", "IssuerCert"); + if (not OwnerConf->ConfigFind("SslForceVersion", "").empty()) + return null_error("The option '%s' is not supported anymore", "SslForceVersion"); - if ((err = gnutls_certificate_allocate_credentials(&tlsFd->credentials)) < 0) - { - _error->Error("Internal error: could not allocate credentials: %s", gnutls_strerror(err)); - return ResultState::FATAL_ERROR; - } + // Delete the existing context and render it unusable + lastHost = ""; + SSL_CTX_free(ctx); + + // We set the context here, but lastHost at the end to only allow reuse of fully initialized contexts + ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == nullptr) + return null_error("Could not create new SSL context: %s", ssl_strerr()); - // Credential setup - std::string fileinfo = OwnerConf->ConfigFind("CaInfo", ""); - if (fileinfo.empty()) + // Load the certificate authorities, either custom or default ones + if (auto const fileinfo = OwnerConf->ConfigFind("CaInfo", ""); not fileinfo.empty()) { - // No CaInfo specified, use system trust store. - err = gnutls_certificate_set_x509_system_trust(tlsFd->credentials); - if (err == 0) - Owner->Warning("No system certificates available. Try installing ca-certificates."); - else if (err < 0) - { - _error->Error("Could not load system TLS certificates: %s", gnutls_strerror(err)); - return ResultState::FATAL_ERROR; - } + if (auto res = SSL_CTX_load_verify_file(ctx, fileinfo.c_str()); res != 1) + return null_error("Could not load certificates from %s (CaInfo option): %s", fileinfo.c_str(), ssl_strerr()); } - else + else if (auto err = SSL_CTX_set_default_verify_paths(ctx); err != 1) + return null_error("Could not load certificates: %s", ssl_strerr()); + + // Client certificate setup, such that clients can authenticate to the server + if (auto const cert = OwnerConf->ConfigFind("SslCert", ""); not cert.empty()) + if (auto res = SSL_CTX_use_certificate_file(ctx, cert.c_str(), SSL_FILETYPE_PEM); res != 1) + return null_error("Could not load client certificate (%s, SslCert option): %s", cert.c_str(), ssl_strerr()); + if (auto const key = OwnerConf->ConfigFind("SslKey", ""); not key.empty()) + if (auto res = SSL_CTX_use_PrivateKey_file(ctx, key.c_str(), SSL_FILETYPE_PEM); res != 1) + return null_error("Could not load client or key (%s, SslKey option): %s", key.c_str(), ssl_strerr()), nullptr; + + // Custom certificate revocation lists. We surely support all the niche cases. + if (auto const crlfile = OwnerConf->ConfigFind("CrlFile", ""); not crlfile.empty()) { - // CA location has been set, use the specified one instead - gnutls_certificate_set_verify_flags(tlsFd->credentials, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - err = gnutls_certificate_set_x509_trust_file(tlsFd->credentials, fileinfo.c_str(), GNUTLS_X509_FMT_PEM); - if (err < 0) - { - _error->Error("Could not load certificates from %s (CaInfo option): %s", fileinfo.c_str(), gnutls_strerror(err)); - return ResultState::FATAL_ERROR; - } + // tell OpenSSL where to find CRL file that is used to check certificate revocation. + // lifted from curl: + // Copyright (c) 1996 - 2023, Daniel Stenberg, <daniel@haxx.se>, and many + // contributors, see the THANKS file. + auto lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(ctx), + X509_LOOKUP_file()); + if (not lookup || not X509_load_crl_file(lookup, crlfile.c_str(), X509_FILETYPE_PEM)) + return null_error("Could not load custom certificate revocation list %s (CrlFile option): %s", crlfile.c_str(), ssl_strerr()); + /* Everything is fine. */ + X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); } - if (not OwnerConf->ConfigFind("IssuerCert", "").empty()) + lastHost = host; + return ctx; +} + +ResultState UnwrapTLS(std::string const &Host, std::unique_ptr<MethodFd> &Fd, + unsigned long const Timeout, aptMethod *const Owner, + aptConfigWrapperForMethods const *const OwnerConf) +{ + if (_config->FindB("Acquire::AllowTLS", true) == false) { - _error->Error("The option '%s' is not supported anymore", "IssuerCert"); + _error->Error("TLS support has been disabled: Acquire::AllowTLS is false."); return ResultState::FATAL_ERROR; } - if (not OwnerConf->ConfigFind("SslForceVersion", "").empty()) - { - _error->Error("The option '%s' is not supported anymore", "SslForceVersion"); + + TlsFd *tlsFd = new TlsFd(); + + tlsFd->hostname = Host; + tlsFd->Timeout = Timeout; + + if (auto ctx = GetContextForHost(Host, OwnerConf)) + tlsFd->ssl = SSL_new(ctx); + else return ResultState::FATAL_ERROR; - } - // For client authentication, certificate file ... - std::string const cert = OwnerConf->ConfigFind("SslCert", ""); - std::string const key = OwnerConf->ConfigFind("SslKey", ""); - if (cert.empty() == false) + FdFd *fdfd = dynamic_cast<FdFd *>(Fd.get()); + if (fdfd != nullptr) { - if ((err = gnutls_certificate_set_x509_key_file( - tlsFd->credentials, - cert.c_str(), - key.empty() ? cert.c_str() : key.c_str(), - GNUTLS_X509_FMT_PEM)) < 0) - { - _error->Error("Could not load client certificate (%s, SslCert option) or key (%s, SslKey option): %s", cert.c_str(), key.c_str(), gnutls_strerror(err)); - return ResultState::FATAL_ERROR; - } + SSL_set_fd(tlsFd->ssl, fdfd->fd); } - - // CRL file - std::string const crlfile = OwnerConf->ConfigFind("CrlFile", ""); - if (crlfile.empty() == false) + else { - if ((err = gnutls_certificate_set_x509_crl_file(tlsFd->credentials, - crlfile.c_str(), - GNUTLS_X509_FMT_PEM)) < 0) - { - _error->Error("Could not load custom certificate revocation list %s (CrlFile option): %s", crlfile.c_str(), gnutls_strerror(err)); + static auto m = NewBioMethod(); + if (m == nullptr) return ResultState::FATAL_ERROR; - } - } - if ((err = gnutls_credentials_set(tlsFd->session, GNUTLS_CRD_CERTIFICATE, tlsFd->credentials)) < 0) - { - _error->Error("Internal error: Could not add certificates to session: %s", gnutls_strerror(err)); - return ResultState::FATAL_ERROR; - } + auto bio = BIO_new(m); + if (bio == nullptr) + return ResultState::FATAL_ERROR; - if ((err = gnutls_set_default_priority(tlsFd->session)) < 0) - { - _error->Error("Internal error: Could not set algorithm preferences: %s", gnutls_strerror(err)); - return ResultState::FATAL_ERROR; + BIO_set_data(bio, Fd.get()); + BIO_up_ref(bio); // the following take one reference each + SSL_set0_rbio(tlsFd->ssl, bio); + SSL_set0_wbio(tlsFd->ssl, bio); } if (OwnerConf->ConfigFindB("Verify-Peer", true)) { - gnutls_session_set_verify_cert(tlsFd->session, OwnerConf->ConfigFindB("Verify-Host", true) ? tlsFd->hostname.c_str() : nullptr, 0); + SSL_set_verify(tlsFd->ssl, SSL_VERIFY_PEER, nullptr); + if (auto res = SSL_set1_host(tlsFd->ssl, OwnerConf->ConfigFindB("Verify-Host", true) ? tlsFd->hostname.c_str() : nullptr); res != 1) + { + _error->Error("Could not set hostname: %s", ssl_strerr()); + return ResultState::FATAL_ERROR; + } } // set SNI only if the hostname is really a name and not an address @@ -1034,23 +1048,37 @@ ResultState UnwrapTLS(std::string const &Host, std::unique_ptr<MethodFd> &Fd, if (inet_pton(AF_INET, tlsFd->hostname.c_str(), &addr4) == 1 || inet_pton(AF_INET6, tlsFd->hostname.c_str(), &addr6) == 1) /* not a host name */; - else if ((err = gnutls_server_name_set(tlsFd->session, GNUTLS_NAME_DNS, tlsFd->hostname.c_str(), tlsFd->hostname.length())) < 0) + else if (auto res = SSL_set_tlsext_host_name(tlsFd->ssl, tlsFd->hostname.c_str()); res != 1) { - _error->Error("Could not set host name %s to indicate to server: %s", tlsFd->hostname.c_str(), gnutls_strerror(err)); + _error->Error("Could not set host name %s to indicate to server: %s", tlsFd->hostname.c_str(), ssl_strerr()); return ResultState::FATAL_ERROR; } } + while (true) + { + auto res = SSL_connect(tlsFd->ssl); + if (res == 1) + break; + switch (auto error = SSL_get_error(tlsFd->ssl, res)) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (not WaitFd(Fd->Fd(), error == SSL_ERROR_WANT_WRITE, Timeout)) + { + _error->Errno("select", "Could not wait for server fd"); + return ResultState::TRANSIENT_ERROR; + } + break; + default: + _error->Error("SSL connection failed: %s / %s", ssl_strerr(), strerror(errno)); + return ResultState::TRANSIENT_ERROR; + } + } + // Set the FD now, so closing it works reliably. tlsFd->UnderlyingFd = std::move(Fd); Fd.reset(tlsFd); - // Do the handshake. - err = tlsFd->DoTLSHandshake(); - - if (err < 0) - return ResultState::TRANSIENT_ERROR; - return ResultState::SUCCESSFUL; } -#endif /*}}}*/ diff --git a/methods/http.cc b/methods/http.cc index 1e76a1f57..678ce3952 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -428,9 +428,7 @@ ResultState HttpServerState::Open() Out.Reset(); Persistent = true; -#ifdef HAVE_GNUTLS bool tls = (ServerName.Access == "https" || APT::String::Endswith(ServerName.Access, "+https")); -#endif // Determine the proxy setting // Used to run AutoDetectProxy(ServerName) here, but we now send a Proxy @@ -455,7 +453,6 @@ ResultState HttpServerState::Open() { char *result = getenv("http_proxy"); Proxy = result ? result : ""; -#ifdef HAVE_GNUTLS if (tls == true) { char *result = getenv("https_proxy"); @@ -464,7 +461,6 @@ ResultState HttpServerState::Open() Proxy = result; } } -#endif } } @@ -478,13 +474,8 @@ ResultState HttpServerState::Open() if (Proxy.empty() == false) Owner->AddProxyAuth(Proxy, ServerName); -#ifdef HAVE_GNUTLS auto const DefaultService = tls ? "https" : "http"; auto const DefaultPort = tls ? 443 : 80; -#else - auto const DefaultService = "http"; - auto const DefaultPort = 80; -#endif if (Proxy.Access == "socks5h") { auto result = Connect(Proxy.Host, Proxy.Port, "socks", 1080, ServerFd, TimeOut, Owner); @@ -518,15 +509,12 @@ ResultState HttpServerState::Open() Port = Proxy.Port; Host = Proxy.Host; -#ifdef HAVE_GNUTLS if (Proxy.Access == "https" && Port == 0) Port = 443; -#endif } auto result = Connect(Host, Port, DefaultService, DefaultPort, ServerFd, TimeOut, Owner); if (result != ResultState::SUCCESSFUL) return result; -#ifdef HAVE_GNUTLS if (Host == Proxy.Host && Proxy.Access == "https") { aptConfigWrapperForMethods ProxyConf{std::vector<std::string>{"http", "https"}}; @@ -541,13 +529,10 @@ ResultState HttpServerState::Open() if (result != ResultState::SUCCESSFUL) return result; } -#endif } -#ifdef HAVE_GNUTLS if (tls) return UnwrapTLS(ServerName.Host, ServerFd, TimeOut, Owner, Owner); -#endif return ResultState::SUCCESSFUL; } |
