summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apt-pkg/acquire-item.cc18
-rw-r--r--apt-pkg/acquire-item.h3
-rw-r--r--apt-pkg/acquire-method.cc6
-rw-r--r--debian/control1
-rw-r--r--methods/gpgv.cc209
-rwxr-xr-xtest/integration/test-cve-2019-3462-dequote-injection66
-rwxr-xr-xtest/integration/test-method-gpgv64
7 files changed, 254 insertions, 113 deletions
diff --git a/apt-pkg/acquire-item.cc b/apt-pkg/acquire-item.cc
index 83c793093..755e1fb59 100644
--- a/apt-pkg/acquire-item.cc
+++ b/apt-pkg/acquire-item.cc
@@ -1464,8 +1464,20 @@ bool pkgAcqMetaBase::CheckDownloadDone(pkgAcqTransactionItem * const I, const st
return true;
}
/*}}}*/
-bool pkgAcqMetaBase::CheckAuthDone(string const &Message) /*{{{*/
+bool pkgAcqMetaBase::CheckAuthDone(string const &Message, pkgAcquire::MethodConfig const *const Cnf) /*{{{*/
{
+ /* If we work with a recent version of our gpgv method, we expect that it tells us
+ which key(s) have signed the file so stuff like CVE-2018-0501 is harder in the future */
+ if (Cnf->Version != "1.0" && LookupTag(Message, "Signed-By").empty())
+ {
+ std::string errmsg;
+ strprintf(errmsg, "Internal Error: Signature on %s seems good, but expected details are missing! (%s)", Target.URI.c_str(), "Signed-By");
+ if (ErrorText.empty())
+ ErrorText = errmsg;
+ Status = StatAuthError;
+ return _error->Error("%s", errmsg.c_str());
+ }
+
// At this point, the gpgv method has succeeded, so there is a
// valid signature from a key in the trusted keyring. We
// perform additional verification of its contents, and use them
@@ -1946,7 +1958,7 @@ void pkgAcqMetaClearSig::Done(std::string const &Message,
QueueForSignatureVerify(this, DestFile, DestFile);
return;
}
- else if(CheckAuthDone(Message) == true)
+ else if (CheckAuthDone(Message, Cnf) == true)
{
if (TransactionManager->IMSHit == false)
TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
@@ -2190,7 +2202,7 @@ void pkgAcqMetaSig::Done(string const &Message, HashStringList const &Hashes,
}
return;
}
- else if(MetaIndex->CheckAuthDone(Message) == true)
+ else if (MetaIndex->CheckAuthDone(Message, Cfg) == true)
{
auto const Releasegpg = GetFinalFilename();
auto const Release = MetaIndex->GetFinalFilename();
diff --git a/apt-pkg/acquire-item.h b/apt-pkg/acquire-item.h
index 4a1378922..70651d9e3 100644
--- a/apt-pkg/acquire-item.h
+++ b/apt-pkg/acquire-item.h
@@ -451,8 +451,9 @@ class APT_HIDDEN pkgAcqMetaBase : public pkgAcqTransactionItem /*{{{*/
*
* \param Message The message block received from the fetch
* subprocess.
+ * \param Cnf The method and its configuration which handled the request
*/
- bool CheckAuthDone(std::string const &Message);
+ bool CheckAuthDone(std::string const &Message, pkgAcquire::MethodConfig const *const Cnf);
/** Check if the current item should fail at this point */
bool CheckStopAuthentication(pkgAcquire::Item * const I, const std::string &Message);
diff --git a/apt-pkg/acquire-method.cc b/apt-pkg/acquire-method.cc
index c9cd48bb6..c67c47ab8 100644
--- a/apt-pkg/acquire-method.cc
+++ b/apt-pkg/acquire-method.cc
@@ -470,6 +470,12 @@ void pkgAcqMethod::Status(const char *Format,...)
* the worker will enqueue again later on to the right queue */
void pkgAcqMethod::Redirect(const string &NewURI)
{
+ if (NewURI.find_first_not_of(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") != std::string::npos)
+ {
+ _error->Error("SECURITY: URL redirect target contains control characters, rejecting.");
+ Fail();
+ return;
+ }
std::unordered_map<std::string, std::string> fields;
try_emplace(fields, "URI", Queue->Uri);
try_emplace(fields, "New-URI", NewURI);
diff --git a/debian/control b/debian/control
index c3481f8c7..18b38ad39 100644
--- a/debian/control
+++ b/debian/control
@@ -16,7 +16,6 @@ Build-Depends: cmake (>= 3.4),
gettext (>= 0.12),
googletest <!nocheck> | libgtest-dev <!nocheck>,
libbz2-dev,
- libcurl4-gnutls-dev (>= 7.19.4~),
libdb-dev,
libgnutls28-dev (>= 3.4.6),
liblz4-dev (>= 0.0~r126),
diff --git a/methods/gpgv.cc b/methods/gpgv.cc
index 9135b49c5..f66e3356f 100644
--- a/methods/gpgv.cc
+++ b/methods/gpgv.cc
@@ -102,21 +102,27 @@ static bool IsTheSameKey(std::string const &validsig, std::string const &goodsig
return validsig.compare(24, 16, goodsig, strlen("GOODSIG "), 16) == 0;
}
+struct APT_HIDDEN SignersStorage {
+ std::vector<std::string> Good;
+ std::vector<std::string> Bad;
+ std::vector<std::string> Worthless;
+ // a worthless signature is a expired or revoked one
+ std::vector<Signer> SoonWorthless;
+ std::vector<std::string> NoPubKey;
+ std::vector<std::string> Valid;
+ std::vector<std::string> SignedBy;
+};
class GPGVMethod : public aptMethod
{
private:
string VerifyGetSigners(const char *file, const char *outfile,
vector<string> const &keyFpts,
vector<string> const &keyFiles,
- vector<string> &GoodSigners,
- vector<string> &BadSigners,
- vector<string> &WorthlessSigners,
- vector<Signer> &SoonWorthlessSigners,
- vector<string> &NoPubKeySigners);
+ SignersStorage &Signers);
protected:
virtual bool URIAcquire(std::string const &Message, FetchItem *Itm) APT_OVERRIDE;
public:
- GPGVMethod() : aptMethod("gpgv","1.0",SingleInstance | SendConfig) {};
+ GPGVMethod() : aptMethod("gpgv", "1.1", SingleInstance | SendConfig){};
};
static void PushEntryWithKeyID(std::vector<std::string> &Signers, char * const buffer, bool const Debug)
{
@@ -146,14 +152,18 @@ static void PushEntryWithUID(std::vector<std::string> &Signers, char * const buf
std::clog << "Got " << msg << " !" << std::endl;
Signers.push_back(msg);
}
+static void implodeVector(std::vector<std::string> const &vec, std::ostream &out, char const * const sep)
+{
+ if (vec.empty())
+ return;
+ std::copy(vec.begin(), std::prev(vec.end()), std::ostream_iterator<std::string>(out, sep));
+ out << *vec.rbegin();
+ return;
+}
string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
vector<string> const &keyFpts,
vector<string> const &keyFiles,
- vector<string> &GoodSigners,
- vector<string> &BadSigners,
- vector<string> &WorthlessSigners,
- vector<Signer> &SoonWorthlessSigners,
- vector<string> &NoPubKeySigners)
+ SignersStorage &Signers)
{
bool const Debug = DebugEnabled();
@@ -171,11 +181,7 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
else if (pid == 0)
{
std::ostringstream keys;
- if (keyFiles.empty() == false)
- {
- std::copy(keyFiles.begin(), keyFiles.end()-1, std::ostream_iterator<std::string>(keys, ","));
- keys << *keyFiles.rbegin();
- }
+ implodeVector(keyFiles, keys, ",");
ExecGPGV(outfile, file, 3, fd, keys.str());
}
close(fd[1]);
@@ -183,7 +189,6 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
FILE *pipein = fdopen(fd[0], "r");
// Loop over the output of apt-key (which really is gnupg), and check the signatures.
- std::vector<std::string> ValidSigners;
std::vector<std::string> ErrSigners;
std::map<std::string, std::vector<std::string>> SubKeyMapping;
size_t buffersize = 0;
@@ -201,25 +206,25 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
// if we improve the apt method communication stuff later
// it will be better.
if (strncmp(buffer, GNUPGBADSIG, sizeof(GNUPGBADSIG)-1) == 0)
- PushEntryWithUID(BadSigners, buffer, Debug);
+ PushEntryWithUID(Signers.Bad, buffer, Debug);
else if (strncmp(buffer, GNUPGERRSIG, sizeof(GNUPGERRSIG)-1) == 0)
PushEntryWithKeyID(ErrSigners, buffer, Debug);
else if (strncmp(buffer, GNUPGNOPUBKEY, sizeof(GNUPGNOPUBKEY)-1) == 0)
{
- PushEntryWithKeyID(NoPubKeySigners, buffer, Debug);
+ PushEntryWithKeyID(Signers.NoPubKey, buffer, Debug);
ErrSigners.erase(std::remove_if(ErrSigners.begin(), ErrSigners.end(), [&](std::string const &errsig) {
return errsig.compare(strlen("ERRSIG "), 16, buffer, sizeof(GNUPGNOPUBKEY), 16) == 0; }), ErrSigners.end());
}
else if (strncmp(buffer, GNUPGNODATA, sizeof(GNUPGNODATA)-1) == 0)
gotNODATA = true;
else if (strncmp(buffer, GNUPGEXPKEYSIG, sizeof(GNUPGEXPKEYSIG)-1) == 0)
- PushEntryWithUID(WorthlessSigners, buffer, Debug);
+ PushEntryWithUID(Signers.Worthless, buffer, Debug);
else if (strncmp(buffer, GNUPGEXPSIG, sizeof(GNUPGEXPSIG)-1) == 0)
- PushEntryWithUID(WorthlessSigners, buffer, Debug);
+ PushEntryWithUID(Signers.Worthless, buffer, Debug);
else if (strncmp(buffer, GNUPGREVKEYSIG, sizeof(GNUPGREVKEYSIG)-1) == 0)
- PushEntryWithUID(WorthlessSigners, buffer, Debug);
+ PushEntryWithUID(Signers.Worthless, buffer, Debug);
else if (strncmp(buffer, GNUPGGOODSIG, sizeof(GNUPGGOODSIG)-1) == 0)
- PushEntryWithKeyID(GoodSigners, buffer, Debug);
+ PushEntryWithKeyID(Signers.Good, buffer, Debug);
else if (strncmp(buffer, GNUPGVALIDSIG, sizeof(GNUPGVALIDSIG)-1) == 0)
{
std::istringstream iss(buffer + sizeof(GNUPGVALIDSIG));
@@ -232,16 +237,16 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
case Digest::State::Weak:
// Treat them like an expired key: For that a message about expiry
// is emitted, a VALIDSIG, but no GOODSIG.
- SoonWorthlessSigners.push_back({sig, digest.name});
+ Signers.SoonWorthless.push_back({sig, digest.name});
if (Debug == true)
std::clog << "Got weak VALIDSIG, key ID: " << sig << std::endl;
break;
case Digest::State::Untrusted:
// Treat them like an expired key: For that a message about expiry
// is emitted, a VALIDSIG, but no GOODSIG.
- WorthlessSigners.push_back(sig);
- GoodSigners.erase(std::remove_if(GoodSigners.begin(), GoodSigners.end(), [&](std::string const &goodsig) {
- return IsTheSameKey(sig, goodsig); }), GoodSigners.end());
+ Signers.Worthless.push_back(sig);
+ Signers.Good.erase(std::remove_if(Signers.Good.begin(), Signers.Good.end(), [&](std::string const &goodsig) {
+ return IsTheSameKey(sig, goodsig); }), Signers.Good.end());
if (Debug == true)
std::clog << "Got untrusted VALIDSIG, key ID: " << sig << std::endl;
break;
@@ -252,7 +257,7 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
break;
}
- ValidSigners.push_back(sig);
+ Signers.Valid.push_back(sig);
if (tokens.size() > 9 && sig != tokens[9])
SubKeyMapping[tokens[9]].emplace_back(sig);
@@ -264,7 +269,7 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
}
fclose(pipein);
free(buffer);
- std::move(ErrSigners.begin(), ErrSigners.end(), std::back_inserter(WorthlessSigners));
+ std::move(ErrSigners.begin(), ErrSigners.end(), std::back_inserter(Signers.Worthless));
// apt-key has a --keyid parameter, but this requires gpg, so we call it without it
// and instead check after the fact which keyids where used for verification
@@ -273,11 +278,11 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
if (Debug == true)
{
std::clog << "GoodSigs needs to be limited to keyid(s): ";
- std::copy(keyFpts.begin(), keyFpts.end(), std::ostream_iterator<std::string>(std::clog, ", "));
+ implodeVector(keyFpts, std::clog, ", ");
std::clog << "\n";
}
std::vector<std::string> filteredGood;
- for (auto &&good: GoodSigners)
+ for (auto &&good: Signers.Good)
{
if (Debug == true)
std::clog << "Key " << good << " is good sig, is it also a valid and allowed one? ";
@@ -293,9 +298,10 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
if (IsTheSameKey(l, good))
{
// GOODSIG might be "just" a longid, so we check VALIDSIG which is always a fingerprint
- if (std::find(ValidSigners.cbegin(), ValidSigners.cend(), l) == ValidSigners.cend())
+ if (std::find(Signers.Valid.cbegin(), Signers.Valid.cend(), l) == Signers.Valid.cend())
continue;
found = true;
+ Signers.SignedBy.push_back(l + "!");
break;
}
else if (exactKey == false)
@@ -306,9 +312,11 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
for (auto const &sub : master->second)
if (IsTheSameKey(sub, good))
{
- if (std::find(ValidSigners.cbegin(), ValidSigners.cend(), sub) == ValidSigners.cend())
+ if (std::find(Signers.Valid.cbegin(), Signers.Valid.cend(), sub) == Signers.Valid.cend())
continue;
found = true;
+ Signers.SignedBy.push_back(l);
+ Signers.SignedBy.push_back(sub + "!");
break;
}
if (found)
@@ -320,10 +328,28 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
if (found)
filteredGood.emplace_back(std::move(good));
else
- NoPubKeySigners.emplace_back(std::move(good));
+ Signers.NoPubKey.emplace_back(std::move(good));
}
- GoodSigners = std::move(filteredGood);
+ Signers.Good= std::move(filteredGood);
+ }
+ else
+ {
+ // for gpg an expired key is valid, too, but we want only the valid & good ones
+ for (auto const &v : Signers.Valid)
+ if (std::any_of(Signers.Good.begin(), Signers.Good.end(),
+ [&v](std::string const &g) { return IsTheSameKey(v, g); }))
+ Signers.SignedBy.push_back(v + "!");
+ for (auto sub : SubKeyMapping)
+ if (std::any_of(sub.second.begin(), sub.second.end(),
+ [&](std::string const &s) {
+ if (std::find(Signers.Valid.begin(), Signers.Valid.end(), s) == Signers.Valid.end())
+ return false;
+ return std::any_of(Signers.Good.begin(), Signers.Good.end(),
+ [&s](std::string const &g) { return IsTheSameKey(s, g); });
+ }))
+ Signers.SignedBy.push_back(sub.first);
}
+ std::sort(Signers.SignedBy.begin(), Signers.SignedBy.end());
int status;
waitpid(pid, &status, 0);
@@ -334,16 +360,20 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
if (Debug)
{
- std::cerr << "Summary:" << std::endl << " Good: ";
- std::copy(GoodSigners.begin(), GoodSigners.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
- std::cerr << std::endl << " Bad: ";
- std::copy(BadSigners.begin(), BadSigners.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
- std::cerr << std::endl << " Worthless: ";
- std::copy(WorthlessSigners.begin(), WorthlessSigners.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
- std::cerr << std::endl << " SoonWorthless: ";
- std::for_each(SoonWorthlessSigners.begin(), SoonWorthlessSigners.end(), [](Signer const &sig) { std::cerr << sig.key << ", "; });
- std::cerr << std::endl << " NoPubKey: ";
- std::copy(NoPubKeySigners.begin(), NoPubKeySigners.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
+ std::cerr << "Summary:\n Good: ";
+ implodeVector(Signers.Good, std::cerr, ", ");
+ std::cerr << "\n Valid: ";
+ implodeVector(Signers.Valid, std::cerr, ", ");
+ std::cerr << "\n Bad: ";
+ implodeVector(Signers.Bad, std::cerr, ", ");
+ std::cerr << "\n Worthless: ";
+ implodeVector(Signers.Worthless, std::cerr, ", ");
+ std::cerr << "\n SoonWorthless: ";
+ std::for_each(Signers.SoonWorthless.begin(), Signers.SoonWorthless.end(), [](Signer const &sig) { std::cerr << sig.key << ", "; });
+ std::cerr << "\n NoPubKey: ";
+ implodeVector(Signers.NoPubKey, std::cerr, ", ");
+ std::cerr << "\n Signed-By: ";
+ implodeVector(Signers.SignedBy, std::cerr, ", ");
std::cerr << std::endl << " NODATA: " << (gotNODATA ? "yes" : "no") << std::endl;
}
@@ -369,12 +399,12 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
{
// gpgv will report success, but we want to enforce a certain keyring
// so if we haven't found the key the valid we found is in fact invalid
- if (GoodSigners.empty())
+ if (Signers.Good.empty())
return _("At least one invalid signature was encountered.");
}
else
{
- if (GoodSigners.empty())
+ if (Signers.Good.empty())
return _("Internal error: Good signature, but could not determine key fingerprint?!");
}
return "";
@@ -391,16 +421,7 @@ bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
{
URI const Get = Itm->Uri;
string const Path = Get.Host + Get.Path; // To account for relative paths
- vector<string> GoodSigners;
- vector<string> BadSigners;
- // a worthless signature is a expired or revoked one
- vector<string> WorthlessSigners;
- vector<Signer> SoonWorthlessSigners;
- vector<string> NoPubKeySigners;
-
- FetchResult Res;
- Res.Filename = Itm->DestFile;
- URIStart(Res);
+ SignersStorage Signers;
std::vector<std::string> keyFpts, keyFiles;
for (auto &&key : VectorizeString(LookupTag(Message, "Signed-By"), ','))
@@ -410,69 +431,83 @@ bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
keyFpts.emplace_back(std::move(key));
// Run apt-key on file, extract contents and get the key ID of the signer
- string const msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), keyFpts, keyFiles,
- GoodSigners, BadSigners, WorthlessSigners,
- SoonWorthlessSigners, NoPubKeySigners);
+ string const msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), keyFpts, keyFiles, Signers);
if (_error->PendingError())
return false;
// Check if all good signers are soon worthless and warn in that case
- if (std::all_of(GoodSigners.begin(), GoodSigners.end(), [&](std::string const &good) {
- return std::any_of(SoonWorthlessSigners.begin(), SoonWorthlessSigners.end(), [&](Signer const &weak) {
+ if (std::all_of(Signers.Good.begin(), Signers.Good.end(), [&](std::string const &good) {
+ return std::any_of(Signers.SoonWorthless.begin(), Signers.SoonWorthless.end(), [&](Signer const &weak) {
return IsTheSameKey(weak.key, good);
});
}))
{
- for (auto const & Signer : SoonWorthlessSigners)
+ for (auto const & Signer : Signers.SoonWorthless)
// TRANSLATORS: The second %s is the reason and is untranslated for repository owners.
Warning(_("Signature by key %s uses weak digest algorithm (%s)"), Signer.key.c_str(), Signer.note.c_str());
}
- if (GoodSigners.empty() || !BadSigners.empty() || !NoPubKeySigners.empty())
+ if (Signers.Good.empty() || !Signers.Bad.empty() || !Signers.NoPubKey.empty())
{
string errmsg;
// In this case, something bad probably happened, so we just go
// with what the other method gave us for an error message.
- if (BadSigners.empty() && WorthlessSigners.empty() && NoPubKeySigners.empty())
+ if (Signers.Bad.empty() && Signers.Worthless.empty() && Signers.NoPubKey.empty())
errmsg = msg;
else
{
- if (!BadSigners.empty())
+ if (!Signers.Bad.empty())
{
errmsg += _("The following signatures were invalid:\n");
- for (vector<string>::iterator I = BadSigners.begin();
- I != BadSigners.end(); ++I)
- errmsg += (*I + "\n");
+ for (auto const &I : Signers.Bad)
+ errmsg.append(I).append("\n");
}
- if (!WorthlessSigners.empty())
+ if (!Signers.Worthless.empty())
{
errmsg += _("The following signatures were invalid:\n");
- for (vector<string>::iterator I = WorthlessSigners.begin();
- I != WorthlessSigners.end(); ++I)
- errmsg += (*I + "\n");
+ for (auto const &I : Signers.Worthless)
+ errmsg.append(I).append("\n");
}
- if (!NoPubKeySigners.empty())
+ if (!Signers.NoPubKey.empty())
{
errmsg += _("The following signatures couldn't be verified because the public key is not available:\n");
- for (vector<string>::iterator I = NoPubKeySigners.begin();
- I != NoPubKeySigners.end(); ++I)
- errmsg += (*I + "\n");
+ for (auto const &I : Signers.NoPubKey)
+ errmsg.append(I).append("\n");
}
}
// this is only fatal if we have no good sigs or if we have at
// least one bad signature. good signatures and NoPubKey signatures
// happen easily when a file is signed with multiple signatures
- if(GoodSigners.empty() or !BadSigners.empty())
- return _error->Error("%s", errmsg.c_str());
+ if (Signers.Good.empty() or !Signers.Bad.empty())
+ return _error->Error("%s", errmsg.c_str());
+ }
+
+ std::unordered_map<std::string, std::string> fields;
+ fields.emplace("URI", Itm->Uri);
+ fields.emplace("Filename", Itm->DestFile);
+ if (Signers.SignedBy.empty() == false)
+ {
+ std::ostringstream out;
+ implodeVector(Signers.SignedBy, out, "\n");
+ fields.emplace("Signed-By", out.str());
+ }
+ {
+ // Just pass the raw output up, because passing it as a real data
+ // structure is too difficult with the method stuff. We keep it
+ // as three separate vectors for future extensibility.
+ std::vector<std::string> gpgvoutput;
+ std::move(Signers.Good.begin(), Signers.Good.end(), std::back_inserter(gpgvoutput));
+ std::move(Signers.Bad.begin(), Signers.Bad.end(), std::back_inserter(gpgvoutput));
+ std::move(Signers.NoPubKey.begin(), Signers.NoPubKey.end(), std::back_inserter(gpgvoutput));
+ if (gpgvoutput.empty() == false)
+ {
+ std::ostringstream out;
+ implodeVector(gpgvoutput, out, "\n");
+ fields.emplace("GPGVOutput", out.str());
+ }
}
-
- // Just pass the raw output up, because passing it as a real data
- // structure is too difficult with the method stuff. We keep it
- // as three separate vectors for future extensibility.
- Res.GPGVOutput = GoodSigners;
- std::move(BadSigners.begin(), BadSigners.end(), std::back_inserter(Res.GPGVOutput));
- std::move(NoPubKeySigners.begin(), NoPubKeySigners.end(), std::back_inserter(Res.GPGVOutput));
- URIDone(Res);
+ SendMessage("201 URI Done", std::move(fields));
+ Dequeue();
if (DebugEnabled())
std::clog << "apt-key succeeded\n";
diff --git a/test/integration/test-cve-2019-3462-dequote-injection b/test/integration/test-cve-2019-3462-dequote-injection
new file mode 100755
index 000000000..a1adec6de
--- /dev/null
+++ b/test/integration/test-cve-2019-3462-dequote-injection
@@ -0,0 +1,66 @@
+#!/bin/sh
+set -e
+
+TESTDIR="$(readlink -f "$(dirname "$0")")"
+. "$TESTDIR/framework"
+setupenvironment
+configarchitecture 'amd64'
+
+# build two uncompressed packages
+buildsimplenativepackage 'alpha' 'all' '1' 'unstable' '' '' 'section' 'optional' '' 'none'
+
+setupaptarchive --no-update
+ORIGINAL_SHA256=$(sha256sum aptarchive/pool/alpha_1_all.deb | awk '{print $1}')
+ORIGINAL_SIZE=$(wc -c aptarchive/pool/alpha_1_all.deb | awk '{print $1}')
+SHA256="DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
+changetowebserver
+
+webserverconfig aptwebserver::redirect::replace::alpha_1_all.deb "beeta_1_all.deb%250a%250a201%2520URI%2520Done%250aURI:%2520http://localhost:${APTHTTPPORT}/pool/beeta_1_all.deb%250aFilename:%2520${TMPWORKINGDIRECTORY}/rootdir/var/cache/apt/archives/partial/alpha_1_all.deb%250aSize:%252020672%250aLast-Modified:%2520Fri,%252018%2520Jan%25202019%252009:52:02%2520+0000%250aSHA256-Hash:%2520${SHA256}%250aChecksum-FileSize-Hash:%252012345%250a%250a%0a"
+
+
+testsuccess apt update -o debug::http=1 -o debug::pkgacquire::worker=1
+
+
+testfailureequal "Reading package lists...
+Building dependency tree...
+The following NEW packages will be installed:
+ alpha
+0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
+Need to get 20.7 kB of archives.
+After this operation, 11.3 kB of additional disk space will be used.
+Err:1 http://localhost:${APTHTTPPORT} unstable/main all alpha all 1
+ SECURITY: URL redirect target contains control characters, rejecting.
+E: Failed to fetch http://localhost:${APTHTTPPORT}/pool/alpha_1_all.deb SECURITY: URL redirect target contains control characters, rejecting.
+E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?" aptget install alpha
+
+
+
+# For reference, the following is the original reproducer/bug. It has
+# been disabled using exit 0, as it will fail in fixed versions.
+exit 0
+
+testfailureequal "Reading package lists...
+Building dependency tree...
+The following NEW packages will be installed:
+ alpha
+0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
+Need to get 20.7 kB of archives.
+After this operation, 11.3 kB of additional disk space will be used.
+Err:1 http://localhost:${APTHTTPPORT} unstable/main all alpha all 1
+ Hash Sum mismatch
+ Hashes of expected file:
+ - SHA256:$ORIGINAL_SHA256
+ - Filesize:$ORIGINAL_SIZE [weak]
+ Hashes of received file:
+ - SHA256:DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
+ - Filesize:12345 [weak]
+ Last modification reported: Fri, 18 Jan 2019 09:52:02 +0000
+E: Failed to fetch http://localhost:${APTHTTPPORT}/pool/beeta_1_all.deb Hash Sum mismatch
+ Hashes of expected file:
+ - SHA256:$ORIGINAL_SHA256
+ - Filesize:$ORIGINAL_SIZE [weak]
+ Hashes of received file:
+ - SHA256:DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
+ - Filesize:12345 [weak]
+ Last modification reported: Fri, 18 Jan 2019 09:52:02 +0000
+E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?" aptget install alpha
diff --git a/test/integration/test-method-gpgv b/test/integration/test-method-gpgv
index b7cf11bdc..70521881d 100755
--- a/test/integration/test-method-gpgv
+++ b/test/integration/test-method-gpgv
@@ -10,7 +10,6 @@ configarchitecture 'i386'
cat > faked-apt-key <<EOF
#!/bin/sh
set -e
-echo "FFOO"
find_gpgv_status_fd() {
while [ -n "\$1" ]; do
if [ "\$1" = '--status-fd' ]; then
@@ -28,45 +27,47 @@ EOF
chmod +x faked-apt-key
testgpgv() {
- echo "$3" > gpgv.output
+ echo "$4" > gpgv.output
msgtest "$1" "$2"
gpgvmethod >method.output 2>&1 || true
- testsuccess --nomsg grep "$2" method.output
+ testsuccess --nomsg grep "^ $2\$" method.output
+ msgtest 'The reported signedby key is' "${3:-empty}"
+ testsuccess --nomsg grep "^ Signed-By:\s\+$3\$" method.output
}
testrun() {
- testgpgv 'Good signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Good signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
- testgpgv 'Good signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Good signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
- testgpgv 'Good subkey signed with long keyid' 'Good: GOODSIG 5B6896415D44C43E,' '[GNUPG:] GOODSIG 5B6896415D44C43E Sebastian Subkey <subkey@example.org>
+ testgpgv 'Good subkey signed with long keyid' 'Good: GOODSIG 5B6896415D44C43E' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE, 4281DEDBD466EAE8C1F4157E5B6896415D44C43E!' '[GNUPG:] GOODSIG 5B6896415D44C43E Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E 2018-08-16 1534459673 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
- testgpgv 'Good subkey signed with fingerprint' 'Good: GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E,' '[GNUPG:] GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E Sebastian Subkey <subkey@example.org>
+ testgpgv 'Good subkey signed with fingerprint' 'Good: GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE, 4281DEDBD466EAE8C1F4157E5B6896415D44C43E!' '[GNUPG:] GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E 2018-08-16 1534459673 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
- testgpgv 'Untrusted signed with long keyid' 'Worthless: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Untrusted signed with long keyid' 'Worthless: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' '' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 1 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^\s\+Good:\s\+$' method.output
- testgpgv 'Untrusted signed with fingerprint' 'Worthless: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Untrusted signed with fingerprint' 'Worthless: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' '' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 1 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^\s\+Good:\s\+$' method.output
- testgpgv 'Weak signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Weak signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 2 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^Message: Signature by key 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE uses weak digest algorithm (SHA1)$' method.output
- testgpgv 'Weak signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+ testgpgv 'Weak signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 2 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^Message: Signature by key 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE uses weak digest algorithm (SHA1)$' method.output
- testgpgv 'No Pubkey with long keyid' 'NoPubKey: NO_PUBKEY E8525D47528144E2,' '[GNUPG:] ERRSIG E8525D47528144E2 1 11 00 1472744666 9
+ testgpgv 'No Pubkey with long keyid' 'NoPubKey: NO_PUBKEY E8525D47528144E2' '' '[GNUPG:] ERRSIG E8525D47528144E2 1 11 00 1472744666 9
[GNUPG:] NO_PUBKEY E8525D47528144E2'
- testgpgv 'No Pubkey with fingerprint' 'NoPubKey: NO_PUBKEY DE66AECA9151AFA1877EC31DE8525D47528144E2,' '[GNUPG:] ERRSIG DE66AECA9151AFA1877EC31DE8525D47528144E2 1 11 00 1472744666 9
+ testgpgv 'No Pubkey with fingerprint' 'NoPubKey: NO_PUBKEY DE66AECA9151AFA1877EC31DE8525D47528144E2' '' '[GNUPG:] ERRSIG DE66AECA9151AFA1877EC31DE8525D47528144E2 1 11 00 1472744666 9
[GNUPG:] NO_PUBKEY DE66AECA9151AFA1877EC31DE8525D47528144E2'
- testgpgv 'Expired key with long keyid' 'Worthless: EXPKEYSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>,' '[GNUPG:] EXPKEYSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>
+ testgpgv 'Expired key with long keyid' 'Worthless: EXPKEYSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>' '' '[GNUPG:] EXPKEYSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>
[GNUPG:] VALIDSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 2016-09-01 1472742629 0 4 0 1 11 00 891CC50E605796A0C6E733F74BC0A39C27CE74F9'
- testgpgv 'Expired key with fingerprint' 'Worthless: EXPKEYSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>,' '[GNUPG:] EXPKEYSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>
+ testgpgv 'Expired key with fingerprint' 'Worthless: EXPKEYSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>' '' '[GNUPG:] EXPKEYSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>
[GNUPG:] VALIDSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 2016-09-01 1472742629 0 4 0 1 11 00 891CC50E605796A0C6E733F74BC0A39C27CE74F9'
}
@@ -111,11 +112,11 @@ Signed-By: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,/dev/null
}
testrun
-testgpgv 'Good signed with long keyid but not signed-by key' 'NoPubKey: GOODSIG 4BC0A39C27CE74F9,' '[GNUPG:] GOODSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>
+testgpgv 'Good signed with long keyid but not signed-by key' 'NoPubKey: GOODSIG 4BC0A39C27CE74F9' '' '[GNUPG:] GOODSIG 4BC0A39C27CE74F9 Rex Expired <rex@example.org>
[GNUPG:] VALIDSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 2016-09-01 1472742625 0 4 0 1 11 00 891CC50E605796A0C6E733F74BC0A39C27CE74F9'
testsuccess grep '^\s\+Good:\s\+$' method.output
testsuccess grep 'verified because the public key is not available: GOODSIG' method.output
-testgpgv 'Good signed with fingerprint but not signed-by key' 'NoPubKey: GOODSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9,' '[GNUPG:] GOODSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>
+testgpgv 'Good signed with fingerprint but not signed-by key' 'NoPubKey: GOODSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9' '' '[GNUPG:] GOODSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 Rex Expired <rex@example.org>
[GNUPG:] VALIDSIG 891CC50E605796A0C6E733F74BC0A39C27CE74F9 2016-09-01 1472742625 0 4 0 1 11 00 891CC50E605796A0C6E733F74BC0A39C27CE74F9'
testsuccess grep '^\s\+Good:\s\+$' method.output
testsuccess grep 'verified because the public key is not available: GOODSIG' method.output
@@ -132,16 +133,37 @@ Filename: /dev/zero
Signed-By: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!
' | runapt "${METHODSDIR}/gpgv"
}
-testgpgv 'Exact matched subkey signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
+testgpgv 'Exact matched subkey signed with long keyid' 'Good: GOODSIG 5A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E'
-testgpgv 'Exact matched subkey signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
+testgpgv 'Exact matched subkey signed with fingerprint' 'Good: GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' '34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE!' '[GNUPG:] GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E'
-testgpgv 'Exact unmatched subkey signed with long keyid' 'NoPubKey: GOODSIG 5B6896415D44C43E,' '[GNUPG:] GOODSIG 5B6896415D44C43E Sebastian Subkey <subkey@example.org>
+testgpgv 'Exact unmatched subkey signed with long keyid' 'NoPubKey: GOODSIG 5B6896415D44C43E' '' '[GNUPG:] GOODSIG 5B6896415D44C43E Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E 2018-08-16 1534459673 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^\s\+Good:\s\+$' method.output
testsuccess grep 'verified because the public key is not available: GOODSIG' method.output
-testgpgv 'Exact unmatched subkey signed with fingerprint' 'NoPubKey: GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E,' '[GNUPG:] GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E Sebastian Subkey <subkey@example.org>
+testgpgv 'Exact unmatched subkey signed with fingerprint' 'NoPubKey: GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' '' '[GNUPG:] GOODSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E Sebastian Subkey <subkey@example.org>
[GNUPG:] VALIDSIG 4281DEDBD466EAE8C1F4157E5B6896415D44C43E 2018-08-16 1534459673 0 4 0 1 11 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE'
testsuccess grep '^\s\+Good:\s\+$' method.output
testsuccess grep 'verified because the public key is not available: GOODSIG' method.output
+
+insertpackage 'unstable' 'foo' 'all' '1'
+setupaptarchive --no-update
+
+echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
+[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output
+testsuccess apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1
+rm -rf rootdir/var/lib/apt/lists
+
+echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>' > gpgv.output
+testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1
+rm -rf rootdir/var/lib/apt/lists
+
+echo '[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output
+testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1
+rm -rf rootdir/var/lib/apt/lists
+
+echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey <subkey@example.org>
+[GNUPG:] VALIDSIG 0000000000000000000000000000000000000000 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output
+testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1
+rm -rf rootdir/var/lib/apt/lists