From da9a05e0b0b2150dbb67090e8b0c3922e46bd5cf Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 7 Dec 2024 16:44:18 +0100 Subject: methods: Add new sqv method The new sqv method uses sequoia's sqv tool to verify files. The tool's interface is quite simple: It returns 0 on success, and prints one line per good signer with the fingerprint. sqv has a configurable crypto policy. We have defined apt-specific override mechanisms for sequoia's standard policy, allowing both users, distributions, and apt package to provide overrides for Sequoia's default policy in meaningful ways. The sqv method will be built and be the standard method for verifying OpenPGP signatures provided that `sqv` is in the PATH during building. It is not built if there was no sqv at build time, so you need a rebuild to enable sqv later on. On the flip side, the gpgv method is always built, but it does need to be always built: If APT::Key::GPGVCommand is set, we need to fallback to it - this is important to support existing users of that interface such as mmdebstrap. Also we want to fall back to it when /usr/bin/sqv disappears - for example in our CI :D A couple of concessions have been made for test suite purposes: - Failure to split a clearsigned file only shows the summary, as the gpgv method also only showed it, and no details why it failed. - We write "Got GOODSIG " in debug mode to mimic the gpgv code to keep the test suite happy. - In various places in the test suite we assert minimal output from sqv, but sqv's human output is not intended to be stable. This will incur additional work when it breaks. However we do not _parse_ the output, so actual operation of apt is unaffected. A couple of things are suboptimal here: - We are still doing clearsigned splitting ourselves. sqv only has support for detached signatures right now, whereas sqopv has support for clearsigned files as well (but sqopv does not provide any reasons for why signature verification fails or means to set a policy). - Deprecation of algorithms happens on a timebomb basis in sequoia. We have no means to give users warnings ahead of time if their configuration is outdated. We have implemented various bits that probably should be going away: - Fallback to trusted.gpg This is just annoying... - Support for fingerprints in Signed-By (no subkey matching though) The extra work here is arguably less compared to gpgv. - Check the keyring for correctness. With Signed-By everywhere, we should just error out rather than skip broken keyrings. Moo --- CMake/config.h.in | 1 + CMakeLists.txt | 1 + apt-pkg/acquire-item.cc | 8 + apt-pkg/contrib/gpgv.cc | 20 +- apt-pkg/contrib/gpgv.h | 9 + methods/CMakeLists.txt | 7 +- methods/sqv.cc | 346 ++++++++++++++++++++++ test/integration/test-apt-update-ims | 2 +- test/integration/test-apt-update-nofallback | 19 +- test/integration/test-apt-update-repeated-ims-hit | 4 + test/integration/test-apt-update-rollback | 9 +- test/integration/test-releasefile-verification | 77 ++--- test/integration/test-signed-by-option | 2 +- 13 files changed, 456 insertions(+), 49 deletions(-) create mode 100644 methods/sqv.cc diff --git a/CMake/config.h.in b/CMake/config.h.in index ced74d8d8..b2c5eac56 100644 --- a/CMake/config.h.in +++ b/CMake/config.h.in @@ -85,3 +85,4 @@ #cmakedefine DEFAULT_PAGER "${DEFAULT_PAGER}" #cmakedefine PAGER_ENV "${PAGER_ENV}" +#cmakedefine SQV_EXECUTABLE "${SQV_EXECUTABLE}" diff --git a/CMakeLists.txt b/CMakeLists.txt index af2ec3a86..03796e09c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ find_package(Iconv REQUIRED) find_package(Perl REQUIRED) find_program(TRIEHASH_EXECUTABLE NAMES triehash) +find_program(SQV_EXECUTABLE NAMES sqv) if (NOT TRIEHASH_EXECUTABLE) message(FATAL_ERROR "Could not find triehash executable") diff --git a/apt-pkg/acquire-item.cc b/apt-pkg/acquire-item.cc index 12524778e..69644883b 100644 --- a/apt-pkg/acquire-item.cc +++ b/apt-pkg/acquire-item.cc @@ -1423,7 +1423,15 @@ string pkgAcqMetaBase::Custom600Headers() const void pkgAcqMetaBase::QueueForSignatureVerify(pkgAcqTransactionItem * const I, std::string const &File, std::string const &Signature) { AuthPass = true; +#ifdef SQV_EXECUTABLE + if (not _config->Find("APT::Key::GPGVCommand").empty() || not FileExists(SQV_EXECUTABLE)) + I->Desc.URI = "gpgv:" + pkgAcquire::URIEncode(Signature); + else { + I->Desc.URI = "sqv:" + pkgAcquire::URIEncode(Signature); + } +#else I->Desc.URI = "gpgv:" + pkgAcquire::URIEncode(Signature); +#endif I->DestFile = File; QueueURI(I->Desc); I->SetActiveSubprocess("gpgv"); diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 7b0a0d240..48d31a44c 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -169,7 +169,7 @@ static bool CheckGPGV(std::unordered_mapError("Signed file isn't valid, got 'NODATA' (does the network require authentication?)"); + } // guess if this is a binary signature, we never officially supported them, // but silently accepted them via passing them unchecked to gpgv if (found_badcontent) @@ -247,6 +254,11 @@ static int VerifyDetachedSignatureFile(std::string FileGPG, int fd[2], int statu return 0; } +bool VerifyDetachedSignatureFile(std::string const &DetachedSignatureFileName) +{ + return VerifyDetachedSignatureFile(DetachedSignatureFileName, nullptr, -1) == 0; +} + std::pair> APT::Internal::FindGPGV(bool Debug) { static thread_local std::unordered_map> checkedCommands; diff --git a/apt-pkg/contrib/gpgv.h b/apt-pkg/contrib/gpgv.h index d4520f3a2..0b84f6bb7 100644 --- a/apt-pkg/contrib/gpgv.h +++ b/apt-pkg/contrib/gpgv.h @@ -96,5 +96,14 @@ APT_PUBLIC bool SplitClearSignedFile(std::string const &InFile, FileFd * const C */ APT_PUBLIC bool OpenMaybeClearSignedFile(std::string const &ClearSignedFileName, FileFd &MessageFile); +/** \brief verifiy a detached signature file for correctness. + * + * We want to expect unavoided content. This may set an error when returning false + * but it might not necessarily do so (in case of empty signature file). + * + * @return true if the detached signature files contains ASCII-armored signatures, otherwise false + */ +APT_PUBLIC bool VerifyDetachedSignatureFile(std::string const &DetachedSignatureFileName); + APT_PUBLIC bool IsAssertedPubKeyAlgo(std::string const &pkstr, std::string const &option); #endif diff --git a/methods/CMakeLists.txt b/methods/CMakeLists.txt index beb08a81d..161cd91d1 100644 --- a/methods/CMakeLists.txt +++ b/methods/CMakeLists.txt @@ -8,6 +8,11 @@ add_executable(file file.cc) add_executable(copy copy.cc) add_executable(store store.cc) add_executable(gpgv gpgv.cc) +if (SQV_EXECUTABLE) +add_executable(sqv sqv.cc) +install(TARGETS sqv + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/methods) +endif() add_executable(cdrom cdrom.cc) add_executable(http http.cc basehttp.cc $) add_executable(mirror mirror.cc) @@ -23,7 +28,7 @@ target_link_libraries(http OpenSSL::SSL $<$:${SYSTEMD_LIB target_link_libraries(rred apt-private) # Install the library -install(TARGETS file copy store gpgv cdrom http rred mirror +install(TARGETS file copy store cdrom gpgv http rred mirror RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/methods) add_links(${CMAKE_INSTALL_LIBEXECDIR}/apt/methods mirror mirror+http mirror+https mirror+file mirror+copy) diff --git a/methods/sqv.cc b/methods/sqv.cc new file mode 100644 index 000000000..63ccdc08f --- /dev/null +++ b/methods/sqv.cc @@ -0,0 +1,346 @@ +#include + +#include "aptmethod.h" +#include +#include +#include +#include +#include +#include + +using std::string; +using std::vector; + +class SQVMethod : public aptMethod +{ + private: + std::optional policy{}; + void SetPolicy(); + bool VerifyGetSigners(const char *file, const char *outfile, + vector keyFiles, + vector &signers); + + protected: + virtual bool URIAcquire(std::string const &Message, FetchItem *Itm) APT_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 keyFiles, + vector &signers) +{ + bool const Debug = DebugEnabled(); + + std::vector 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()) + { + auto Parts = GetListOfFilesInDir(_config->FindDir("Dir::Etc::TrustedParts"), std::vector{"gpg", "asc"}, true); + 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); + } + + // 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 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) +{ + URI const Get(Itm->Uri); + std::string const Path = DecodeSendURI(Get.Host + Get.Path); // To account for relative paths + + std::vector 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)); + } + + // Run apt-key on file, extract contents and get the key ID of the signer + VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), keyFiles, Signers); + if (_error->PendingError()) + { + // Legacy fallback to trusted.gpg + auto trusted = _config->FindFile("Dir::Etc::Trusted"); + _error->PushToStack(); + VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), {trusted}, Signers); + bool error = _error->PendingError(); + _error->RevertToStack(); + if (error) + return false; + std::string warning; + strprintf(warning, + _("Key is stored in legacy trusted.gpg keyring (%s). Use Signed-By instead. See the USER CONFIGURATION section in apt-secure(8) for details."), + trusted.c_str()); + Warning(std::move(warning)); + } + + if (Signers.empty()) + return _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 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"; + + return true; +} + +int main() +{ + return SQVMethod().Run(); +} diff --git a/test/integration/test-apt-update-ims b/test/integration/test-apt-update-ims index 1894c3adf..101c2f298 100755 --- a/test/integration/test-apt-update-ims +++ b/test/integration/test-apt-update-ims @@ -48,7 +48,7 @@ runtest() { # ensure that we still do a hash check for other files on ims hit of Release if grep -q '^Hit:[0-9]\+ .* InRelease$' expected.output || ! grep -q '^Ign:[0-9]\+ .* Release\(\.gpg\)\?$' expected.output; then - $TEST aptget update -o Debug::Acquire::gpgv=1 $APTOPT + $TEST aptget update -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 $APTOPT cp rootdir/tmp/${TEST}.output goodsign.output testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)" testsuccess grep '^Got GOODSIG ' goodsign.output diff --git a/test/integration/test-apt-update-nofallback b/test/integration/test-apt-update-nofallback index 18f12b1e3..c9ee75756 100755 --- a/test/integration/test-apt-update-nofallback +++ b/test/integration/test-apt-update-nofallback @@ -198,9 +198,16 @@ test_inrelease_to_invalid_inrelease() sed -i 's/^Codename:.*/Codename: evil!/' "$APTARCHIVE/dists/unstable/InRelease" inject_evil_package - testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) + if test -e /usr/bin/sqv; then + # FIXME: Do not assert sqv output + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: Sub-process /usr/bin/sqv returned an error code (1), error message is: Verifying signature: Message has been manipulated +W: Failed to fetch file:${APTARCHIVE}/dists/unstable/InRelease Sub-process /usr/bin/sqv returned an error code (1), error message is: Verifying signature: Message has been manipulated +W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + else + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) W: Failed to fetch file:${APTARCHIVE}/dists/unstable/InRelease The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + fi # ensure we keep the repo testfailure grep 'evil' rootdir/var/lib/apt/lists/*InRelease @@ -218,10 +225,16 @@ test_release_gpg_to_invalid_release_release_gpg() # now subvert Release do no longer verify echo "Some evil data" >> "$APTARCHIVE/dists/unstable/Release" inject_evil_package - - testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable Release: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) + if test -e /usr/bin/sqv; then + # FIXME: Do not assert sqv output + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable Release: Sub-process /usr/bin/sqv returned an error code (1), error message is: Verifying signature: Message has been manipulated +W: Failed to fetch file:${APTARCHIVE}/dists/unstable/Release.gpg Sub-process /usr/bin/sqv returned an error code (1), error message is: Verifying signature: Message has been manipulated +W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + else + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable Release: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) W: Failed to fetch file:${APTARCHIVE}/dists/unstable/Release.gpg The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + fi testfailure grep 'evil' rootdir/var/lib/apt/lists/*Release testfileequal lists.before "$(listcurrentlistsdirectory)" diff --git a/test/integration/test-apt-update-repeated-ims-hit b/test/integration/test-apt-update-repeated-ims-hit index 74d46b31b..43871a31f 100755 --- a/test/integration/test-apt-update-repeated-ims-hit +++ b/test/integration/test-apt-update-repeated-ims-hit @@ -40,7 +40,11 @@ rm -f rootdir/etc/apt/trusted.gpg.d/* sed -i -e 's#^deb #deb [trusted=yes] #' rootdir/etc/apt/sources.list.d/* APTARCHIVE="$(readlink -f ./aptarchive)" +if test -e /usr/bin/sqv; then +GPGERROR="W: GPG error: file:$APTARCHIVE Release: The signatures couldn't be verified because no keyring is specified" +else GPGERROR="W: GPG error: file:$APTARCHIVE Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 5A90D141DBAC8DAE" +fi msgmsg 'Running update again does not change result' '0' testwarningmsg "$GPGERROR" apt update diff --git a/test/integration/test-apt-update-rollback b/test/integration/test-apt-update-rollback index 8235968bb..68505f6ba 100755 --- a/test/integration/test-apt-update-rollback +++ b/test/integration/test-apt-update-rollback @@ -156,9 +156,16 @@ test_inrelease_to_unauth_inrelease() { signreleasefiles 'Marvin Paranoid' - testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY E8525D47528144E2 + if test -e /usr/bin/sqv; then + # FIXME: Do not assert sqv output + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: Sub-process /usr/bin/sqv returned an error code (1), error message is: Missing key DE66AECA9151AFA1877EC31DE8525D47528144E2, which is needed to verify signature. +W: Failed to fetch file:$APTARCHIVE/dists/unstable/InRelease Sub-process /usr/bin/sqv returned an error code (1), error message is: Missing key DE66AECA9151AFA1877EC31DE8525D47528144E2, which is needed to verify signature. +W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + else + testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file:${APTARCHIVE} unstable InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY E8525D47528144E2 W: Failed to fetch file:$APTARCHIVE/dists/unstable/InRelease The following signatures couldn't be verified because the public key is not available: NO_PUBKEY E8525D47528144E2 W: Some index files failed to download. They have been ignored, or old ones used instead." aptget update -qq + fi testfileequal lists.before "$(listcurrentlistsdirectory)" testnotempty find "${ROOTDIR}/var/lib/apt/lists" -name '*_InRelease' diff --git a/test/integration/test-releasefile-verification b/test/integration/test-releasefile-verification index 1392a05d1..00c72d7d9 100755 --- a/test/integration/test-releasefile-verification +++ b/test/integration/test-releasefile-verification @@ -97,13 +97,20 @@ echo -n 'apt' > aptarchive/apt.deb PKGFILE="${TESTDIR}/$(echo "$(basename "$0")" | sed 's#^test-#Packages-#')" updatewithwarnings() { - testwarning aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testwarning aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 testsuccess grep -E "$1" rootdir/tmp/testwarning.output } foreachgpg() { rm -f rootdir/etc/apt/apt.conf.d/00gpgvcmd - "$@" + local sqv="true" + if [ "$1" = "--no-sqv" ]; then + local sqv= + shift + fi + if [ "$sqv" ]; then + "$@" + fi for GPGV in "gpgv-sq" "gpgv-g10code"; do msgmsg "Forcing $GPGV to be used" echo "APT::Key::GPGVCommand \"$GPGV\";" > "rootdir/etc/apt/apt.conf.d/00gpgvcmd" @@ -147,7 +154,7 @@ runtest() { rm -rf rootdir/var/lib/apt/lists cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg signreleasefiles 'Rex Expired' - updatewithwarnings '^W: .* EXPKEYSIG' + updatewithwarnings '^W: .* (EXPKEYSIG|Expired)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt failaptold @@ -157,7 +164,7 @@ runtest() { prepare "${PKGFILE}" rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Joe Sixpack,Marvin Paranoid' - successfulaptgetupdate 'NO_PUBKEY' + successfulaptgetupdate 'NO_PUBKEY\|GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold @@ -167,7 +174,7 @@ runtest() { rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Joe Sixpack,Rex Expired' cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg - successfulaptgetupdate 'EXPKEYSIG' + successfulaptgetupdate 'EXPKEYSIG\|GOODSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' rm -f rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt @@ -177,7 +184,7 @@ runtest() { prepare "${PKGFILE}" rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Marvin Paranoid' - updatewithwarnings '^W: .* NO_PUBKEY' + updatewithwarnings '^W: .* (NO_PUBKEY|Missing key)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt failaptold @@ -202,7 +209,7 @@ runtest() { msgmsg 'Good warm archive signed by' 'Marvin Paranoid' prepare "${PKGFILE}-new" signreleasefiles 'Marvin Paranoid' - updatewithwarnings '^W: .* NO_PUBKEY' + updatewithwarnings '^W: .* (NO_PUBKEY|Missing key)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold @@ -212,7 +219,7 @@ runtest() { # Use a colon here to test weird filenames too cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rex:expired.gpg signreleasefiles 'Rex Expired' - updatewithwarnings '^W: .* EXPKEYSIG' + updatewithwarnings '^W: .* (EXPKEYSIG|Expired)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold @@ -230,7 +237,7 @@ runtest() { rm -rf rootdir/var/lib/apt/lists local MARVIN="$(readlink -f keys/marvinparanoid.pub)" sed -i "s#^\(deb\(-src\)\?\) #\1 [signed-by=$MARVIN] #" rootdir/etc/apt/sources.list.d/* - updatewithwarnings '^W: .* NO_PUBKEY' + updatewithwarnings '^W: .* (NO_PUBKEY|Missing key)' msgmsg 'Cold archive signed by good keyring' 'Marvin Paranoid' prepare "${PKGFILE}" @@ -265,7 +272,7 @@ runtest() { rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Joe Sixpack' sed -i "s#^\(deb\(-src\)\?\) #\1 [signed-by=$MARVIN] #" rootdir/etc/apt/sources.list.d/* - updatewithwarnings '^W: .* be verified because the public key is not available: .*' + updatewithwarnings '^W: .* (be verified because the public key is not available: .*|No good signature from required signer)' msgmsg 'Cold archive signed by good keyid' 'Marvin Paranoid' rm -rf rootdir/var/lib/apt/lists @@ -279,7 +286,7 @@ runtest() { msgmsg 'Cold archive signed by good keyid' 'Marvin Paranoid,Joe Sixpack' rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Marvin Paranoid,Joe Sixpack' - successfulaptgetupdate 'NoPubKey: GOODSIG' + successfulaptgetupdate 'NoPubKey: GOODSIG\|DE66AECA9151AFA1877EC31DE8525D47528144E2' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold @@ -324,7 +331,7 @@ Signed-By: ${SIXPACK}" rootdir/var/lib/apt/lists/*Release sed -i "/^Valid-Until: / a\ Signed-By: ${MARVIN}" rootdir/var/lib/apt/lists/*Release touch -d 'now - 1 year' rootdir/var/lib/apt/lists/*Release - updatewithwarnings 'W: .* public key is not available: GOODSIG' + updatewithwarnings 'W: .* (public key is not available: GOODSIG|No good signature from required signer:)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold @@ -373,22 +380,26 @@ Signed-By: ${SEBASTIAN}" rootdir/var/lib/apt/lists/*Release sed -i "/^Valid-Until: / a\ Signed-By: ${SEBASTIAN}!" rootdir/var/lib/apt/lists/*Release touch -d 'now - 1 year' rootdir/var/lib/apt/lists/*Release - updatewithwarnings 'W: .* public key is not available: GOODSIG' + updatewithwarnings 'W: .* (public key is not available: GOODSIG|No good signature from required signer:)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold local SUBKEY="4281DEDBD466EAE8C1F4157E5B6896415D44C43E" + # This is only supported for gpgv, so require an explicit gpgv command, + # otherwise the default verification backend may be sqv. + if [ "$GPGV" ]; then msgmsg 'Warm archive with correct exact subkey signing' 'Sebastian Subkey' rm -rf rootdir/var/lib/apt/lists cp -a rootdir/var/lib/apt/lists-bak rootdir/var/lib/apt/lists - sed -i "/^Valid-Until: / a\ + sed -i "/^Valid-Until: / a\ Signed-By: ${SUBKEY}!" rootdir/var/lib/apt/lists/*Release - touch -d 'now - 1 year' rootdir/var/lib/apt/lists/*Release - successfulaptgetupdate - testsuccessequal "$(cat "${PKGFILE}-new") + touch -d 'now - 1 year' rootdir/var/lib/apt/lists/*Release + successfulaptgetupdate + testsuccessequal "$(cat "${PKGFILE}-new") " aptcache show apt - installaptnew + installaptnew + fi rm -f rootdir/etc/apt/trusted.gpg.d/sebastiansubkey.gpg } @@ -419,16 +430,6 @@ runtest2() { find aptarchive -name Release -exec sh -c "echo Hello > {}.gpg" \; testfailuremsg "E: GPG error: http://localhost:${APTHTTPPORT} Release: Signed file isn't valid, got 'NODATA' (does the network require authentication?)" aptget update - msgmsg 'Cold archive signed by' 'Empty inline' - rm -rf rootdir/var/lib/apt/lists - find aptarchive -name Release -exec sh -c '( echo -----BEGIN PGP SIGNED MESSAGE-----; echo Hash: SHA512; echo; cat {} ; echo; echo -----BEGIN PGP SIGNATURE-----; echo ; echo -----END PGP SIGNATURE----- ) > $(dirname {})/In$(basename {})' \; - testfailuremsg "E: GPG error: http://localhost:${APTHTTPPORT} InRelease: Signed file isn't valid, got 'NODATA' (does the network require authentication?)" aptget update - - msgmsg 'Cold archive signed by' 'Bad inline' - rm -rf rootdir/var/lib/apt/lists - find aptarchive -name Release -exec sh -c '( echo -----BEGIN PGP SIGNED MESSAGE-----; echo Hash: SHA512; echo; cat {} ; echo; echo -----BEGIN PGP SIGNATURE-----; echo ; echo SGVsbG8K; echo =nFa9; echo -----END PGP SIGNATURE----- ) > $(dirname {})/In$(basename {})' \; - testfailuremsg "E: GPG error: http://localhost:${APTHTTPPORT} InRelease: Signed file isn't valid, got 'NODATA' (does the network require authentication?)" aptget update - find aptarchive/ \( -name InRelease -o -name Release.gpg \) -delete # Unsigned archive from the beginning must also be detected. @@ -463,7 +464,7 @@ EOF export APT_TESTS_DIGEST_ALGO='SHA512' successfulaptgetupdate() { - testsuccess aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testsuccess aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 if [ -n "$1" ]; then cp rootdir/tmp/testsuccess.output aptupdate.output testsuccess grep "$1" aptupdate.output @@ -472,13 +473,13 @@ successfulaptgetupdate() { foreachgpg runtest3 'Trusted' successfulaptgetupdate() { - testwarning aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testwarning aptget update -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 if [ -n "$1" ]; then testsuccess grep "$1" rootdir/tmp/testwarning.output fi testsuccess grep 'uses weak algorithm' rootdir/tmp/testwarning.output } -foreachgpg runtest3 'Weak' +foreachgpg --no-sqv runtest3 'Weak' msgmsg "Running test with apt-untrusted digest" echo "APT::Hashes::$APT_TESTS_DIGEST_ALGO::Untrusted \"yes\";" > rootdir/etc/apt/apt.conf.d/truststate @@ -489,14 +490,14 @@ runfailure() { prepare "${PKGFILE}" rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Joe Sixpack' - testfailure aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 - testsuccess grep 'The following signatures were invalid' rootdir/tmp/testfailure.output + testfailure aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 + testsuccess grep 'The following signatures were invalid\|MD5 is not considered secure' rootdir/tmp/testfailure.output testnopackage 'apt' - testwarning aptget update --allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testwarning aptget update --allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 failaptold rm -rf rootdir/var/lib/apt/lists sed -i 's#^deb\(-src\)\? #deb\1 [allow-insecure=yes] #' rootdir/etc/apt/sources.list.d/* - testwarning aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testwarning aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 failaptold sed -i 's#^deb\(-src\)\? \[allow-insecure=yes\] #deb\1 #' rootdir/etc/apt/sources.list.d/* @@ -504,13 +505,13 @@ runfailure() { prepare "${PKGFILE}" rm -rf rootdir/var/lib/apt/lists signreleasefiles 'Marvin Paranoid' - testfailure aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 + testfailure aptget update --no-allow-insecure-repositories -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 -o Debug::Acquire::sqv=1 testnopackage 'apt' - updatewithwarnings '^W: .* NO_PUBKEY' + updatewithwarnings '^W: .* (NO_PUBKEY|Missing key)' testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt failaptold export APT_DONT_SIGN='Release.gpg' done } -foreachgpg runfailure +foreachgpg --no-sqv runfailure diff --git a/test/integration/test-signed-by-option b/test/integration/test-signed-by-option index 8e1e9a8c2..e6a3fbb86 100755 --- a/test/integration/test-signed-by-option +++ b/test/integration/test-signed-by-option @@ -66,7 +66,7 @@ xSigned-By: -----END PGP PUBLIC KEY BLOCK----- EOF testfailure apt update -o Debug::Acquire::gpgv=1 -testsuccess grep "NO_PUBKEY 5A90D141DBAC8DAE" rootdir/tmp/testfailure.output +testsuccess grep -E "(NO_PUBKEY 5A90D141DBAC8DAE|no keyring is specified)" rootdir/tmp/testfailure.output sed -i s/^xSigned-By/Signed-By/ rootdir/etc/apt/sources.list.d/deb822.sources testsuccess apt update -o Debug::Acquire::gpgv=1 # make sure we did not leave leftover files (LP: #1995247) -- cgit v1.2.3-70-g09d2