1
0
Fork 0
apt/methods/sqv.cc
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

403 lines
12 KiB
C++

#include <config.h>
#include "aptmethod.h"
#include <apt-pkg/gpgv.h>
#include <apt-pkg/strutl.h>
#include <iterator>
#include <optional>
#include <ostream>
#include <sstream>
using std::string;
using std::vector;
class SQVMethod : public aptMethod
{
private:
std::optional<std::string> policy{};
void SetPolicy();
bool VerifyGetSigners(const char *file, const char *outfile,
vector<string> keyFiles,
vector<string> &signers);
bool ExecuteSqv(const std::vector<std::string> &args, std::vector<std::string> &signers);
protected:
bool URIAcquire(std::string const &Message, FetchItem *Itm) override;
public:
SQVMethod();
};
SQVMethod::SQVMethod() : aptMethod("sqv", "1.1", SingleInstance | SendConfig | SendURIEncoded)
{
}
void SQVMethod::SetPolicy()
{
constexpr const char *policies[] = {
// APT overrides
"APT_SEQUOIA_CRYPTO_POLICY",
"/etc/crypto-policies/back-ends/apt-sequoia.config",
"/var/lib/crypto-config/profiles/current/apt-sequoia.config",
// Sequoia overrides
"SEQUOIA_CRYPTO_POLICY",
"/etc/crypto-policies/back-ends/sequoia.config",
"/var/lib/crypto-config/profiles/current/sequoia.config",
// Fallback APT defaults
"/usr/share/apt/default-sequoia.config",
};
if (policy)
return;
policy = "";
for (auto policy : policies)
{
if (not strchr(policy, '/'))
{
if (auto value = getenv(policy))
{
this->policy = value;
break;
}
}
else if (FileExists(policy))
{
this->policy = policy;
break;
}
}
if (not policy->empty())
{
if (DebugEnabled())
std::clog << "Setting SEQUOIA_CRYPTO_POLICY=" << *policy << std::endl;
setenv("SEQUOIA_CRYPTO_POLICY", policy->c_str(), 1);
}
}
bool SQVMethod::VerifyGetSigners(const char *file, const char *outfile,
vector<string> keyFiles,
vector<string> &signers)
{
bool const Debug = DebugEnabled();
std::vector<std::string> args;
SetPolicy();
args.push_back(SQV_EXECUTABLE);
auto dearmorKeyOrCheckFormat = [&](std::string const &k) -> bool
{
_error->PushToStack();
FileFd keyFd(k, FileFd::ReadOnly);
_error->RevertToStack();
if (not keyFd.IsOpen())
return _error->Warning("The key(s) in the keyring %s are ignored as the file is not readable by user executing gpgv.\n", k.c_str());
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;
return true;
}
else
{
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)
goto err;
return true;
}
err:
return _error->Warning("The key(s) in the keyring %s are ignored as the file has an unsupported filetype.", k.c_str());
};
if (keyFiles.empty())
{
// Either trusted or trustedparts must exist
_error->PushToStack();
auto Parts = GetListOfFilesInDir(_config->FindDir("Dir::Etc::TrustedParts"), std::vector<std::string>{"gpg", "asc"}, true);
if (auto trusted = _config->FindFile("Dir::Etc::Trusted"); not trusted.empty())
{
std::string s;
strprintf(s, "Loading %s from deprecated option Dir::Etc::Trusted\n", trusted.c_str());
Warning(std::move(s));
Parts.push_back(trusted);
}
if (Parts.empty())
_error->MergeWithStack();
else
_error->RevertToStack();
for (auto &Part : Parts)
{
if (Debug)
std::clog << "Trying TrustedPart: " << Part << std::endl;
if (struct stat st; stat(Part.c_str(), &st) != 0 || st.st_size == 0)
continue;
if (not dearmorKeyOrCheckFormat(Part))
{
std::string msg;
_error->PopMessage(msg);
if (not msg.empty())
Warning(std::move(msg));
continue;
}
keyFiles.push_back(Part);
}
}
if (keyFiles.empty())
return _error->Error("The signatures couldn't be verified because no keyring is specified");
for (auto const &keyring : keyFiles)
{
args.push_back("--keyring");
args.push_back(keyring);
}
FileFd signatureFd;
FileFd messageFd;
DEFER([&]
{
if (signatureFd.IsOpen()) RemoveFile("RemoveSignature", signatureFd.Name());
if (messageFd.IsOpen()) RemoveFile("RemoveMessage", messageFd.Name()); });
if (strcmp(file, outfile) == 0)
{
if (GetTempFile("apt.sig", false, &signatureFd) == nullptr)
return false;
if (GetTempFile("apt.data", false, &messageFd) == nullptr)
return false;
// FIXME: The test suite only expects the final message.
_error->PushToStack();
if (signatureFd.Failed() || messageFd.Failed() ||
not SplitClearSignedFile(file, &messageFd, nullptr, &signatureFd))
return _error->RevertToStack(), _error->Error("Splitting up %s into data and signature failed", file);
_error->RevertToStack();
args.push_back(signatureFd.Name());
args.push_back(messageFd.Name());
}
else
{
if (not VerifyDetachedSignatureFile(file))
return false;
args.push_back(file);
args.push_back(outfile);
}
bool res = false;
std::vector<std::string> aheadErrors;
// Check the signature with a one-year-ahead policy first
{
_error->PushToStack();
auto time = std::time(nullptr);
auto tm = std::localtime(&time);
std::string yearAheadDate;
strprintf(yearAheadDate, "%d-%d-%d", tm->tm_year + 1900 + 1, tm->tm_mon + 1, tm->tm_mday);
args.push_back("--policy-as-of");
args.push_back(std::move(yearAheadDate));
res = ExecuteSqv(args, signers);
args.pop_back();
args.pop_back();
// Preserve any warnings or whatnot on success
if (res)
_error->MergeWithStack();
else
{
while (not _error->empty())
{
std::string msg;
_error->PopMessage(msg);
aheadErrors.push_back(msg);
}
_error->RevertToStack();
}
}
// The year-ahead-policy produced no valid signer, check if valid at current time.
if (not res)
{
// clear signers, args have already been cleaned post-execution
signers.clear();
res = ExecuteSqv(args, signers);
if (res)
{
Warning(_("Policy will reject signature within a year, see --audit for details"));
for (auto &&msg : aheadErrors)
Audit(std::move(msg));
}
}
return res;
}
bool SQVMethod::ExecuteSqv(const std::vector<std::string> &args, std::vector<std::string> &signers)
{
bool const Debug = DebugEnabled();
// FIXME: Use a select() loop
FileFd sqvout;
FileFd sqverr;
if (GetTempFile("apt.sqvout", false, &sqvout) == nullptr)
return "Internal error: Cannot create temporary file";
DEFER([&]
{ RemoveFile("CleanSQVOut", sqvout.Name()); });
if (GetTempFile("apt.sqverr", false, &sqverr) == nullptr)
return "Internal error: Cannot create temporary file";
DEFER([&]
{ RemoveFile("CleanSQVErr", sqverr.Name()); });
// Translate the argument list to a C array. This should happen before
// the fork so we don't allocate money between fork() and execvp().
if (Debug)
std::clog << "Executing " << APT::String::Join(args, " ") << std::endl;
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);
pid_t pid = ExecFork({sqvout.Fd(), sqverr.Fd()});
if (pid < 0)
return _error->Errno("VerifyGetSigners", "Couldn't spawn new process");
else if (pid == 0)
{
dup2(sqvout.Fd(), STDOUT_FILENO);
dup2(sqverr.Fd(), STDERR_FILENO);
execvp(cArgs[0], (char **)&cArgs[0]);
_exit(123);
}
int status;
waitpid(pid, &status, 0);
sqverr.Seek(0);
sqvout.Seek(0);
if (Debug == true)
ioprintf(std::clog, "sqv exited with status %i\n", WEXITSTATUS(status));
if (WEXITSTATUS(status) != 0)
{
std::string msg;
for (std::string err; sqverr.ReadLine(err);)
msg.append(err).append("\n");
return _error->Error(_("Sub-process %s returned an error code (%u), error message is:\n%s"), cArgs[0], WEXITSTATUS(status), msg.c_str());
}
for (std::string signer; sqvout.ReadLine(signer);)
{
if (Debug)
std::clog << "Got GOODSIG " << signer << std::endl;
signers.push_back(signer);
}
return true;
}
static std::string GenerateKeyFile(std::string const key)
{
FileFd fd;
GetTempFile("apt-key.XXXXXX.asc", false, &fd);
fd.Write(key.data(), key.size());
return fd.Name();
}
bool SQVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
{
// Quick safety check: do we have left-over errors from a previous URL?
if (unlikely(_error->PendingError()))
return _error->Error("Internal error: Error set at start of verification");
URI const Get(Itm->Uri);
std::string const Path = DecodeSendURI(Get.Host + Get.Path); // To account for relative paths
std::vector<std::string> Signers, keyFpts, keyFiles;
struct TemporaryFile
{
std::string name = "";
~TemporaryFile() { RemoveFile("~TemporaryFile", name); }
} tmpKey;
std::string SignedBy = DeQuoteString(LookupTag(Message, "Signed-By"));
if (SignedBy.find("-----BEGIN PGP PUBLIC KEY BLOCK-----") != std::string::npos)
{
tmpKey.name = GenerateKeyFile(SignedBy);
keyFiles.emplace_back(tmpKey.name);
}
else
{
for (auto &&key : VectorizeString(SignedBy, ','))
if (key.empty() == false && key[0] == '/')
keyFiles.emplace_back(std::move(key));
else
keyFpts.emplace_back(std::move(key));
}
// Nothing should have failed here in the setup, if it did, don't bother verifying
if (_error->PendingError())
return false;
// Run sqv on file, extract contents and get the key ID of the signer
VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), keyFiles, Signers);
if (Signers.empty())
return _error->PendingError() ? false : _error->Error("No good signature");
if (not keyFpts.empty())
{
Signers.erase(std::remove_if(Signers.begin(), Signers.end(), [&](std::string const &signer)
{
bool allowedSigner = std::find(keyFpts.begin(), keyFpts.end(), signer) != keyFpts.end();
if (not allowedSigner && DebugEnabled())
std::cerr << "NoPubKey: GOODSIG " << signer << "\n";
return not allowedSigner; }),
Signers.end());
if (Signers.empty())
{
if (keyFpts.size() > 1)
return _error->Error(_("No good signature from required signers: %s"), APT::String::Join(keyFpts, ", ").c_str());
return _error->Error(_("No good signature from required signer: %s"), APT::String::Join(keyFpts, ", ").c_str());
}
}
std::unordered_map<std::string, std::string> fields;
fields.emplace("URI", Itm->Uri);
fields.emplace("Filename", Itm->DestFile);
fields.emplace("Signed-By", APT::String::Join(Signers, "\n"));
SendMessage("201 URI Done", std::move(fields));
Dequeue();
if (DebugEnabled())
std::clog << "sqv succeeded\n";
// If we have a pending error somehow, we should still fail here...
return not _error->PendingError();
}
int main()
{
return SQVMethod().Run();
}