summaryrefslogtreecommitdiff
path: root/apt-pkg/contrib/gpgv.cc
diff options
context:
space:
mode:
Diffstat (limited to 'apt-pkg/contrib/gpgv.cc')
-rw-r--r--apt-pkg/contrib/gpgv.cc254
1 files changed, 190 insertions, 64 deletions
diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc
index 147f15d9d..43c634dde 100644
--- a/apt-pkg/contrib/gpgv.cc
+++ b/apt-pkg/contrib/gpgv.cc
@@ -18,11 +18,13 @@
#include <unistd.h>
#include <algorithm>
+#include <forward_list>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
+#include <unordered_map>
#include <vector>
#include <apti18n.h>
@@ -112,7 +114,9 @@ static bool operator!=(LineBuffer const &buf, std::string_view const exp) noexce
And as a cherry on the cake, we use our apt-key wrapper to do part
of the lifting in regards to merging keyrings. Fun for the whole family.
*/
-static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...)
+#define apt_error(...) apt_msg("ERROR", __VA_ARGS__)
+#define apt_warning(...) apt_msg("WARNING", __VA_ARGS__)
+static void APT_PRINTF(5) apt_msg(std::string const &tag, std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...)
{
std::ostringstream outstr;
std::ostream &out = (statusfd == -1) ? outterm : outstr;
@@ -128,53 +132,197 @@ static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, i
}
if (statusfd != -1)
{
- auto const errtag = "[APTKEY:] ERROR ";
+ auto const errtag = "[APTKEY:] " + tag + " ";
outstr << '\n';
auto const errtext = outstr.str();
- if (not FileFd::Write(fd[1], errtag, strlen(errtag)) ||
+ if (not FileFd::Write(fd[1], errtag.data(), errtag.size()) ||
not FileFd::Write(fd[1], errtext.data(), errtext.size()))
outterm << errtext << std::flush;
}
}
+
+static bool CheckGPGV(std::unordered_map<std::string, std::forward_list<std::string>> &checkedCommands, std::string gpgv, bool Debug)
+{
+ if (checkedCommands.find(gpgv) == checkedCommands.end())
+ {
+ // Create entry
+ checkedCommands[gpgv];
+ FileFd dumpOptions;
+ pid_t child;
+ const char *argv[] = {gpgv.c_str(), "--dump-options", nullptr};
+ if (unlikely(Debug))
+ std::clog << "Executing " << gpgv << " --dump-options" << std::endl;
+ if (not Popen(argv, dumpOptions, child, FileFd::ReadOnly) && Debug)
+ return false;
+
+ for (std::string line; dumpOptions.ReadLine(line);)
+ {
+ if (unlikely(Debug))
+ std::clog << "Read line: " << line << std::endl;
+ checkedCommands[gpgv].push_front(APT::String::Strip(line));
+ }
+ dumpOptions.Close();
+ waitpid(child, NULL, 0);
+ }
+ return not checkedCommands[gpgv].empty();
+}
+
+std::pair<std::string, std::forward_list<std::string>> APT::Internal::FindGPGV(bool Debug)
+{
+ static thread_local std::unordered_map<std::string, std::forward_list<std::string>> checkedCommands;
+ const std::string gpgvVariants[] = {
+ _config->Find("Apt::Key::gpgvcommand"),
+ // 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))
+ return std::make_pair(gpgv, checkedCommands[gpgv]);
+ return {};
+}
+
void ExecGPGV(std::string const &File, std::string const &FileGPG,
int const &statusfd, int fd[2], std::string const &key)
{
- #define EINTERNAL 111
- std::string const aptkey = _config->Find("Dir::Bin::apt-key", CMAKE_INSTALL_FULL_BINDIR "/apt-key");
+ auto const keyFiles = VectorizeString(key, ',');
+ ExecGPGV(File, FileGPG, statusfd, fd, keyFiles);
+}
+void ExecGPGV(std::string const &File, std::string const &FileGPG,
+ int const &statusfd, int fd[2], std::vector<std::string> const &KeyFiles)
+{
+#define EINTERNAL 111
bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
struct exiter {
- std::vector<const char *> files;
+ std::vector<std::string> files;
void operator ()(int code) APT_NORETURN {
- std::for_each(files.begin(), files.end(), unlink);
+ std::for_each(files.begin(), files.end(), [](auto f)
+ { unlink(f.c_str()); });
exit(code);
}
} local_exit;
+ auto [gpgv, supportedOptions] = APT::Internal::FindGPGV(Debug);
+ if (gpgv.empty())
+ {
+ apt_error(std::cerr, statusfd, fd, "Couldn't find a gpgv binary");
+ local_exit(EINTERNAL);
+ }
- std::vector<const char *> Args;
+ std::vector<std::string> Args;
Args.reserve(10);
- Args.push_back(aptkey.c_str());
- Args.push_back("--quiet");
- Args.push_back("--readonly");
- auto const keysFileFpr = VectorizeString(key, ',');
- for (auto const &k: keysFileFpr)
+ Args.push_back(gpgv);
+ Args.push_back("--ignore-time-conflict");
+
+ auto dearmorKeyOrCheckFormat = [&](std::string const &k) -> std::string
+ {
+ FileFd keyFd(k, FileFd::ReadOnly);
+ if (not keyFd.IsOpen())
+ {
+ apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file is not readable by user executing apt-key.\n", k.c_str());
+ return "";
+ }
+ if (APT::String::Endswith(k, ".gpg") || APT::String::Endswith(k, ".pub"))
+ {
+ unsigned char c;
+ if (not keyFd.Read(&c, sizeof(c)))
+ goto err;
+ // Identify the leading byte of an OpenPGP public key packet
+ // 0x98 -- old-format OpenPGP public key packet, up to 255 octets
+ // 0x99 -- old-format OpenPGP public key packet, 256-65535 octets
+ // 0xc6 -- new-format OpenPGP public key packet, any length
+ if (c == 0x98 || c == 0x99 || c == 0xc6)
+ return k;
+ }
+ else if (APT::String::Endswith(k, ".asc"))
+ {
+ std::string b64msg;
+ int state = 0;
+ for (std::string line; keyFd.ReadLine(line);)
+ {
+ line = APT::String::Strip(line);
+ if (APT::String::Startswith(line, "-----BEGIN PGP PUBLIC KEY BLOCK-----"))
+ state = 1;
+ else if (state == 1 && line == "")
+ state = 2;
+ else if (state == 2 && line != "" && line[0] != '=' && line[0] != '-')
+ b64msg += line;
+ else if (APT::String::Startswith(line, "-----END"))
+ state = 3;
+ }
+ if (state != 3)
+ goto err;
+
+ FileFd dearmoredFd;
+ if (GetTempFile("apt.XXXXXX.gpg", false, &dearmoredFd) == nullptr)
+ local_exit(EINTERNAL);
+ if (auto decoded = Base64Decode(b64msg); not decoded.empty())
+ dearmoredFd.Write(decoded.data(), decoded.size());
+ local_exit.files.push_back(dearmoredFd.Name());
+ return dearmoredFd.Name();
+ }
+ err:
+ apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file has an unsupported filetype.", k.c_str());
+ return "";
+ };
+ auto maybeAddKeyring = [&](std::string const &k)
+ {
+ if (struct stat st; stat(k.c_str(), &st) != 0 || st.st_size == 0)
+ return;
+ if (auto cleanKey = dearmorKeyOrCheckFormat(k); not cleanKey.empty())
+ {
+ Args.push_back("--keyring");
+ Args.push_back(cleanKey);
+ }
+ return;
+ };
+
+ for (auto const &k : KeyFiles)
{
if (unlikely(k.empty()))
continue;
if (k[0] == '/')
{
- Args.push_back("--keyring");
- Args.push_back(k.c_str());
+ if (Debug)
+ std::clog << "Trying Signed-By: " << k << std::endl;
+
+ maybeAddKeyring(k);
}
else
{
Args.push_back("--keyid");
- Args.push_back(k.c_str());
+ Args.push_back(k);
}
}
- Args.push_back("verify");
+
+ std::vector<std::string> Parts;
+ if (std::find(Args.begin(), Args.end(), "--keyring") == Args.end())
+ {
+ Parts = GetListOfFilesInDir(_config->FindDir("Dir::Etc::TrustedParts"), std::vector<std::string>{"gpg", "asc"}, true);
+ if (char *env = getenv("APT_KEY_NO_LEGACY_KEYRING"); env == nullptr || not StringToBool(env, false))
+ Parts.insert(Parts.begin(), _config->FindFile("Dir::Etc::Trusted"));
+ for (auto &Part : Parts)
+ {
+ if (Debug)
+ std::clog << "Trying TrustedPart: " << Part << std::endl;
+ maybeAddKeyring(Part);
+ }
+ }
+
+ // If we do not give it any keyring, gpgv shouts keydb errors at us
+ if (std::find(Args.begin(), Args.end(), "--keyring") == Args.end())
+ {
+ Args.push_back("--keyring");
+ Args.push_back("/dev/null");
+ }
char statusfdstr[10];
if (statusfd != -1)
@@ -184,6 +332,12 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG,
Args.push_back(statusfdstr);
}
+ if (auto assertPubkeyAlgo = _config->Find("Apt::Key::assert-pubkey-algo"); not assertPubkeyAlgo.empty())
+ {
+ if (std::find(supportedOptions.begin(), supportedOptions.end(), "--assert-pubkey-algo") != supportedOptions.end())
+ Args.push_back("--assert-pubkey-algo=" + assertPubkeyAlgo);
+ }
+
Configuration::Item const *Opts;
Opts = _config->Tree("Acquire::gpgv::Options");
if (Opts != 0)
@@ -193,42 +347,11 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG,
{
if (Opts->Value.empty())
continue;
- Args.push_back(Opts->Value.c_str());
+ Args.push_back(Opts->Value);
}
}
enum { DETACHED, CLEARSIGNED } releaseSignature = (FileGPG != File) ? DETACHED : CLEARSIGNED;
- std::unique_ptr<char, FreeDeleter> sig;
- std::unique_ptr<char, FreeDeleter> data;
- std::unique_ptr<char, FreeDeleter> conf;
-
- // Dump the configuration so apt-key picks up the correct Dir values
- {
- {
- std::string tmpfile;
- strprintf(tmpfile, "%s/apt.conf.XXXXXX", GetTempDir().c_str());
- conf.reset(strdup(tmpfile.c_str()));
- }
- if (conf == nullptr) {
- apt_error(std::cerr, statusfd, fd, "Couldn't create tempfile names for passing config to apt-key");
- local_exit(EINTERNAL);
- }
- int confFd = mkstemp(conf.get());
- if (confFd == -1) {
- apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", conf.get());
- local_exit(EINTERNAL);
- }
- local_exit.files.push_back(conf.get());
-
- std::ofstream confStream(conf.get());
- close(confFd);
- _config->Dump(confStream);
- confStream.close();
- setenv("APT_CONFIG", conf.get(), 1);
- }
-
- // Tell apt-key not to emit warnings
- setenv("APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE", "1", 1);
if (releaseSignature == DETACHED)
{
@@ -307,21 +430,19 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG,
local_exit(112);
}
- Args.push_back(FileGPG.c_str());
- Args.push_back(File.c_str());
+ Args.push_back(FileGPG);
+ Args.push_back(File);
}
else // clear-signed file
{
FileFd signature;
if (GetTempFile("apt.sig", false, &signature) == nullptr)
local_exit(EINTERNAL);
- sig.reset(strdup(signature.Name().c_str()));
- local_exit.files.push_back(sig.get());
+ local_exit.files.push_back(signature.Name());
FileFd message;
if (GetTempFile("apt.data", false, &message) == nullptr)
local_exit(EINTERNAL);
- data.reset(strdup(message.Name().c_str()));
- local_exit.files.push_back(data.get());
+ local_exit.files.push_back(message.Name());
if (signature.Failed() || message.Failed() ||
not SplitClearSignedFile(File, &message, nullptr, &signature))
@@ -329,17 +450,15 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG,
apt_error(std::cerr, statusfd, fd, "Splitting up %s into data and signature failed", File.c_str());
local_exit(112);
}
- Args.push_back(sig.get());
- Args.push_back(data.get());
+ Args.push_back(signature.Name());
+ Args.push_back(message.Name());
}
- Args.push_back(NULL);
-
if (Debug)
{
std::clog << "Preparing to exec: ";
- for (std::vector<const char *>::const_iterator a = Args.begin(); *a != NULL; ++a)
- std::clog << " " << *a;
+ for (auto const &a : Args)
+ std::clog << " " << a;
std::clog << std::endl;
}
@@ -360,20 +479,27 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG,
putenv((char *)"LC_MESSAGES=");
}
+ // Translate the argument list to a C array. This should happen before
+ // the fork so we don't allocate money between fork() and execvp().
+ std::vector<const char *> cArgs;
+ cArgs.reserve(Args.size() + 1);
+ for (auto const &arg : Args)
+ cArgs.push_back(arg.c_str());
+ cArgs.push_back(nullptr);
// We have created tempfiles we have to clean up
// and we do an additional check, so fork yet another time …
pid_t pid = ExecFork();
if(pid < 0) {
- apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0], File.c_str());
+ apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0].c_str(), File.c_str());
local_exit(EINTERNAL);
}
if(pid == 0)
{
if (statusfd != -1)
dup2(fd[1], statusfd);
- execvp(Args[0], (char **) &Args[0]);
- apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0], File.c_str());
+ execvp(cArgs[0], (char **) &cArgs[0]);
+ apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0].c_str(), File.c_str());
local_exit(EINTERNAL);
}