Upstream debian-apt-team/apt state of the touched files before d61b22c353
This commit is contained in:
commit
1a7c061cfd
1 file changed
+418
+418
@@ -0,0 +1,418 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string buf;
|
||||
for (auto k : keyFiles)
|
||||
{
|
||||
_error->PushToStack();
|
||||
FileFd keyFd(k, FileFd::ReadOnly);
|
||||
_error->RevertToStack();
|
||||
if (keyFd.IsOpen())
|
||||
continue;
|
||||
|
||||
strprintf(buf, "The key(s) in the keyring %s are ignored as the file is not readable by user executing gpgv.\n", k.c_str());
|
||||
Warning(std::move(buf));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user