diff options
author | Julian Andres Klode <jak@debian.org> | 2018-10-14 19:23:41 +0000 |
---|---|---|
committer | Julian Andres Klode <jak@debian.org> | 2018-10-14 19:23:41 +0000 |
commit | b80e48783c183aeaf1d30d898a7743f091d96336 (patch) | |
tree | 7a0e3711dd68bbd8fdfd0d07f9af6f33aa9d2d51 | |
parent | bb2f6c8c2a965ac1ff01582b93e64da8991dcbfc (diff) | |
parent | 8375d5b58038fc026098dcccc3de87cd9d740334 (diff) |
Merge branch 'feature/subkeys' into 'master'
Support subkeys and multiple keyrings in Signed-By options
See merge request apt-team/apt!27
-rw-r--r-- | apt-pkg/contrib/gpgv.cc | 11 | ||||
-rw-r--r-- | apt-pkg/deb/debmetaindex.cc | 113 | ||||
-rw-r--r-- | cmdline/apt-key.in | 157 | ||||
-rw-r--r-- | doc/sources.list.5.xml | 29 | ||||
-rw-r--r-- | methods/gpgv.cc | 82 | ||||
-rw-r--r-- | test/integration/framework | 1 | ||||
-rw-r--r-- | test/integration/sebastiansubkey.master.sec | bin | 0 -> 4829 bytes | |||
-rw-r--r-- | test/integration/sebastiansubkey.pub | bin | 0 -> 2567 bytes | |||
-rw-r--r-- | test/integration/sebastiansubkey.sec | bin | 0 -> 3546 bytes | |||
-rwxr-xr-x | test/integration/test-apt-key | 16 | ||||
-rwxr-xr-x | test/integration/test-method-gpgv | 49 | ||||
-rwxr-xr-x | test/integration/test-releasefile-verification | 69 | ||||
-rwxr-xr-x | test/integration/test-signed-by-option | 22 |
13 files changed, 383 insertions, 166 deletions
diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index cc1fbc5aa..f8ab8d715 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -108,17 +108,20 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(aptkey.c_str()); Args.push_back("--quiet"); Args.push_back("--readonly"); - if (key.empty() == false) + auto const keysFileFpr = VectorizeString(key, ','); + for (auto const &k: keysFileFpr) { - if (key[0] == '/') + if (unlikely(k.empty())) + continue; + if (k[0] == '/') { Args.push_back("--keyring"); - Args.push_back(key.c_str()); + Args.push_back(k.c_str()); } else { Args.push_back("--keyid"); - Args.push_back(key.c_str()); + Args.push_back(k.c_str()); } } Args.push_back("verify"); diff --git a/apt-pkg/deb/debmetaindex.cc b/apt-pkg/deb/debmetaindex.cc index 9c7c70784..1afcdf2c0 100644 --- a/apt-pkg/deb/debmetaindex.cc +++ b/apt-pkg/deb/debmetaindex.cc @@ -28,6 +28,57 @@ #include <apti18n.h> +static std::string transformFingergrpints(std::string finger) /*{{{*/ +{ + std::transform(finger.begin(), finger.end(), finger.begin(), ::toupper); + if (finger.length() == 40) + { + if (finger.find_first_not_of("0123456789ABCDEF") == std::string::npos) + return finger; + } + else if (finger.length() == 41) + { + auto bang = finger.find_first_not_of("0123456789ABCDEF"); + if (bang == 40 && finger[bang] == '!') + return finger; + } + return ""; +} + /*}}}*/ +static std::string transformFingergrpintsWithFilenames(std::string const &finger) /*{{{*/ +{ + // no check for existence as we could be chrooting later or such things + if (finger.empty() == false && finger[0] == '/') + return finger; + return transformFingergrpints(finger); +} + /*}}}*/ +static std::string NormalizeSignedBy(std::string SignedBy, bool const SupportFilenames) /*{{{*/ +{ + // we could go all fancy and allow short/long/string matches as gpgv/apt-key does, + // but fingerprints are harder to fake than the others and this option is set once, + // not interactively all the time so easy to type is not really a concern. + std::transform(SignedBy.begin(), SignedBy.end(), SignedBy.begin(), [](char const c) { + return (isspace(c) == 0) ? c : ','; + }); + auto fingers = VectorizeString(SignedBy, ','); + auto const isAnEmptyString = [](std::string const &s) { return s.empty(); }; + fingers.erase(std::remove_if(fingers.begin(), fingers.end(), isAnEmptyString), fingers.end()); + if (unlikely(fingers.empty())) + return ""; + if (SupportFilenames) + std::transform(fingers.begin(), fingers.end(), fingers.begin(), transformFingergrpintsWithFilenames); + else + std::transform(fingers.begin(), fingers.end(), fingers.begin(), transformFingergrpints); + if (std::any_of(fingers.begin(), fingers.end(), isAnEmptyString)) + return ""; + std::stringstream os; + std::copy(fingers.begin(), fingers.end() - 1, std::ostream_iterator<std::string>(os, ",")); + os << *fingers.rbegin(); + return os.str(); +} + /*}}}*/ + class APT_HIDDEN debReleaseIndexPrivate /*{{{*/ { public: @@ -566,26 +617,9 @@ bool debReleaseIndex::Load(std::string const &Filename, std::string * const Erro auto Sign = Section.FindS("Signed-By"); if (Sign.empty() == false) { - std::transform(Sign.begin(), Sign.end(), Sign.begin(), [&](char const c) { - return (isspace(c) == 0) ? c : ','; - }); - auto fingers = VectorizeString(Sign, ','); - std::transform(fingers.begin(), fingers.end(), fingers.begin(), [&](std::string finger) { - std::transform(finger.begin(), finger.end(), finger.begin(), ::toupper); - if (finger.length() != 40 || finger.find_first_not_of("0123456789ABCDEF") != std::string::npos) - { - if (ErrorText != NULL) - strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Signed-By", Filename.c_str()); - return std::string(); - } - return finger; - }); - if (fingers.empty() == false && std::find(fingers.begin(), fingers.end(), "") == fingers.end()) - { - std::stringstream os; - std::copy(fingers.begin(), fingers.end(), std::ostream_iterator<std::string>(os, ",")); - SignedBy = os.str(); - } + SignedBy = NormalizeSignedBy(Sign, false); + if (SignedBy.empty() && ErrorText != NULL) + strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Signed-By", Filename.c_str()); } if (AuthPossible) @@ -737,38 +771,15 @@ bool debReleaseIndex::SetSignedBy(std::string const &pSignedBy) { if (SignedBy.empty() == true && pSignedBy.empty() == false) { - if (pSignedBy[0] == '/') // no check for existence as we could be chrooting later or such things - SignedBy = pSignedBy; // absolute path to a keyring file - else - { - // we could go all fancy and allow short/long/string matches as gpgv/apt-key does, - // but fingerprints are harder to fake than the others and this option is set once, - // not interactively all the time so easy to type is not really a concern. - auto fingers = VectorizeString(pSignedBy, ','); - std::transform(fingers.begin(), fingers.end(), fingers.begin(), [&](std::string finger) { - std::transform(finger.begin(), finger.end(), finger.begin(), ::toupper); - if (finger.length() != 40 || finger.find_first_not_of("0123456789ABCDEF") != std::string::npos) - { - _error->Error(_("Invalid value set for option %s regarding source %s %s (%s)"), "Signed-By", URI.c_str(), Dist.c_str(), "not a fingerprint"); - return std::string(); - } - return finger; - }); - std::stringstream os; - std::copy(fingers.begin(), fingers.end(), std::ostream_iterator<std::string>(os, ",")); - SignedBy = os.str(); - } - // Normalize the string: Remove trailing commas - while (SignedBy[SignedBy.size() - 1] == ',') - SignedBy.resize(SignedBy.size() - 1); + SignedBy = NormalizeSignedBy(pSignedBy, true); + if (SignedBy.empty()) + _error->Error(_("Invalid value set for option %s regarding source %s %s (%s)"), "Signed-By", URI.c_str(), Dist.c_str(), "not a fingerprint"); } - else { - // Only compare normalized strings - auto pSignedByView = APT::StringView(pSignedBy); - while (pSignedByView[pSignedByView.size() - 1] == ',') - pSignedByView = pSignedByView.substr(0, pSignedByView.size() - 1); - if (pSignedByView != SignedBy) - return _error->Error(_("Conflicting values set for option %s regarding source %s %s: %s != %s"), "Signed-By", URI.c_str(), Dist.c_str(), SignedBy.c_str(), pSignedByView.to_string().c_str()); + else + { + auto const normalSignedBy = NormalizeSignedBy(pSignedBy, true); + if (normalSignedBy != SignedBy) + return _error->Error(_("Conflicting values set for option %s regarding source %s %s: %s != %s"), "Signed-By", URI.c_str(), Dist.c_str(), SignedBy.c_str(), normalSignedBy.c_str()); } return true; } diff --git a/cmdline/apt-key.in b/cmdline/apt-key.in index 7ec1b034c..e9187b423 100644 --- a/cmdline/apt-key.in +++ b/cmdline/apt-key.in @@ -15,6 +15,74 @@ eval "$(apt-config shell ARCHIVE_KEYRING_URI APT::Key::ArchiveKeyringURI)" aptkey_echo() { echo "$@"; } +find_gpgv_status_fd() { + while [ -n "$1" ]; do + if [ "$1" = '--status-fd' ]; then + shift + echo "$1" + break + fi + shift + done +} +GPGSTATUSFD="$(find_gpgv_status_fd "$@")" + +apt_warn() { + if [ -z "$GPGHOMEDIR" ]; then + echo >&2 'W:' "$@" + else + echo 'W:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" + fi + if [ -n "$GPGSTATUSFD" ]; then + echo >&${GPGSTATUSFD} '[APTKEY:] WARNING' "$@" + fi +} +apt_error() { + if [ -z "$GPGHOMEDIR" ]; then + echo >&2 'E:' "$@" + else + echo 'E:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" + fi + if [ -n "$GPGSTATUSFD" ]; then + echo >&${GPGSTATUSFD} '[APTKEY:] ERROR' "$@" + fi +} + +cleanup_gpg_home() { + if [ -z "$GPGHOMEDIR" ]; then return; fi + if [ -s "$GPGHOMEDIR/aptwarnings.log" ]; then + cat >&2 "$GPGHOMEDIR/aptwarnings.log" + fi + if command_available 'gpgconf'; then + GNUPGHOME="${GPGHOMEDIR}" gpgconf --kill all >/dev/null 2>&1 || true + fi + rm -rf "$GPGHOMEDIR" +} + +# gpg needs (in different versions more or less) files to function correctly, +# so we give it its own homedir and generate some valid content for it later on +create_gpg_home() { + # for cases in which we want to cache a homedir due to expensive setup + if [ -n "$GPGHOMEDIR" ]; then + return + fi + if [ -n "$TMPDIR" ]; then + # tmpdir is a directory and current user has rwx access to it + # same tests as in apt-pkg/contrib/fileutl.cc GetTempDir() + if [ ! -d "$TMPDIR" ] || [ ! -r "$TMPDIR" ] || [ ! -w "$TMPDIR" ] || [ ! -x "$TMPDIR" ]; then + unset TMPDIR + fi + fi + GPGHOMEDIR="$(mktemp --directory --tmpdir 'apt-key-gpghome.XXXXXXXXXX')" + CURRENTTRAP="${CURRENTTRAP} cleanup_gpg_home;" + trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM + if [ -z "$GPGHOMEDIR" ]; then + apt_error "Could not create temporary gpg home directory in $TMPDIR (wrong permissions?)" + exit 28 + fi + chmod 700 "$GPGHOMEDIR" +} + requires_root() { if [ "$(id -u)" -ne 0 ]; then apt_error "This command can only be used by root." @@ -282,7 +350,7 @@ foreach_keyring_do() { shift # if a --keyring was given, just work on this one if [ -n "$FORCED_KEYRING" ]; then - $ACTION "$TRUSTEDFILE" "$@" + $ACTION "$FORCED_KEYRING" "$@" else # otherwise all known keyrings are up for inspection if accessible_file_exists "$TRUSTEDFILE" && is_supported_keyring "$TRUSTEDFILE"; then @@ -525,11 +593,26 @@ while [ -n "$1" ]; do case "$1" in --keyring) shift - TRUSTEDFILE="$1" - FORCED_KEYRING="$1" + if [ -z "$FORCED_KEYRING" -o "$FORCED_KEYRING" = '/dev/null' ]; then + TRUSTEDFILE="$1" + FORCED_KEYRING="$1" + elif [ "$TRUSTEDFILE" = "$FORCED_KEYRING" ]; then + create_gpg_home + FORCED_KEYRING="${GPGHOMEDIR}/mergedkeyrings.gpg" + echo -n '' > "$FORCED_KEYRING" + chmod 0644 -- "$FORCED_KEYRING" + catfile "$TRUSTEDFILE" "$FORCED_KEYRING" + catfile "$1" "$FORCED_KEYRING" + else + catfile "$1" "$FORCED_KEYRING" + fi ;; --keyid) shift + if [ -n "$FORCED_KEYID" ]; then + apt_error 'Specifying --keyid multiple times is not supported' + exit 1 + fi FORCED_KEYID="$1" ;; --secret-keyring) @@ -582,74 +665,6 @@ if [ -z "$command" ]; then fi shift -find_gpgv_status_fd() { - while [ -n "$1" ]; do - if [ "$1" = '--status-fd' ]; then - shift - echo "$1" - break - fi - shift - done -} -GPGSTATUSFD="$(find_gpgv_status_fd "$@")" - -apt_warn() { - if [ -z "$GPGHOMEDIR" ]; then - echo >&2 'W:' "$@" - else - echo 'W:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" - fi - if [ -n "$GPGSTATUSFD" ]; then - echo >&${GPGSTATUSFD} '[APTKEY:] WARNING' "$@" - fi -} -apt_error() { - if [ -z "$GPGHOMEDIR" ]; then - echo >&2 'E:' "$@" - else - echo 'E:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" - fi - if [ -n "$GPGSTATUSFD" ]; then - echo >&${GPGSTATUSFD} '[APTKEY:] ERROR' "$@" - fi -} - -cleanup_gpg_home() { - if [ -z "$GPGHOMEDIR" ]; then return; fi - if [ -s "$GPGHOMEDIR/aptwarnings.log" ]; then - cat >&2 "$GPGHOMEDIR/aptwarnings.log" - fi - if command_available 'gpgconf'; then - GNUPGHOME="${GPGHOMEDIR}" gpgconf --kill all >/dev/null 2>&1 || true - fi - rm -rf "$GPGHOMEDIR" -} - -# gpg needs (in different versions more or less) files to function correctly, -# so we give it its own homedir and generate some valid content for it later on -create_gpg_home() { - # for cases in which we want to cache a homedir due to expensive setup - if [ -n "$GPGHOMEDIR" ]; then - return - fi - if [ -n "$TMPDIR" ]; then - # tmpdir is a directory and current user has rwx access to it - # same tests as in apt-pkg/contrib/fileutl.cc GetTempDir() - if [ ! -d "$TMPDIR" ] || [ ! -r "$TMPDIR" ] || [ ! -w "$TMPDIR" ] || [ ! -x "$TMPDIR" ]; then - unset TMPDIR - fi - fi - GPGHOMEDIR="$(mktemp --directory --tmpdir 'apt-key-gpghome.XXXXXXXXXX')" - CURRENTTRAP="${CURRENTTRAP} cleanup_gpg_home;" - trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM - if [ -z "$GPGHOMEDIR" ]; then - apt_error "Could not create temporary gpg home directory in $TMPDIR (wrong permissions?)" - exit 28 - fi - chmod 700 "$GPGHOMEDIR" -} - prepare_gpg_home() { # crude detection if we are called from a maintainerscript where the # package depends on gnupg or not. We accept recommends here as diff --git a/doc/sources.list.5.xml b/doc/sources.list.5.xml index 84eb527e7..eaea13ae5 100644 --- a/doc/sources.list.5.xml +++ b/doc/sources.list.5.xml @@ -14,7 +14,7 @@ &apt-email; &apt-product; <!-- The last update date --> - <date>2018-02-27T00:00:00Z</date> + <date>2018-08-17T00:00:00Z</date> </refentryinfo> <refmeta> @@ -294,17 +294,22 @@ deb-src [ option1=value1 option2=value2 ] uri suite [component1] [component2] [. </para></listitem> <listitem><para><option>Signed-By</option> (<option>signed-by</option>) - is either an absolute path to a keyring file (has to be - accessible and readable for the <literal>_apt</literal> user, - so ensure everyone has read-permissions on the file) or one or - more fingerprints of keys either in the - <filename>trusted.gpg</filename> keyring or in the - keyrings in the <filename>trusted.gpg.d/</filename> directory - (see <command>apt-key fingerprint</command>). If the option is - set, only the key(s) in this keyring or only the keys with these - fingerprints are used for the &apt-secure; verification of this - repository. Defaults to the value of the option with the same name - if set in the previously acquired <filename>Release</filename> file. + is an option to require a repository to pass &apt-secure; verification + with a certain set of keys rather than all trusted keys apt has configured. + It is specified as a list of absolute paths to keyring files (have to be + accessible and readable for the <literal>_apt</literal> system user, + so ensure everyone has read-permissions on the file) and fingerprints + of keys to select from these keyrings. If no keyring files are specified + the default is the <filename>trusted.gpg</filename> keyring and + all keyrings in the <filename>trusted.gpg.d/</filename> directory + (see <command>apt-key fingerprint</command>). If no fingerprint is + specified all keys in the keyrings are selected. A fingerprint will + accept also all signatures by a subkey of this key, if this isn't + desired an exclamation mark (<literal>!</literal>) can be appended to + the fingerprint to disable this behaviour. + The option defaults to the value of the option with the same name + if set in the previously acquired <filename>Release</filename> file + of this repository (only fingerprints can be specified there through). Otherwise all keys in the trusted keyrings are considered valid signers for this repository. </para></listitem> diff --git a/methods/gpgv.cc b/methods/gpgv.cc index 84b8c3e59..9135b49c5 100644 --- a/methods/gpgv.cc +++ b/methods/gpgv.cc @@ -20,6 +20,7 @@ #include <array> #include <iostream> #include <iterator> +#include <map> #include <sstream> #include <string> #include <vector> @@ -105,7 +106,8 @@ class GPGVMethod : public aptMethod { private: string VerifyGetSigners(const char *file, const char *outfile, - std::string const &key, + vector<string> const &keyFpts, + vector<string> const &keyFiles, vector<string> &GoodSigners, vector<string> &BadSigners, vector<string> &WorthlessSigners, @@ -145,7 +147,8 @@ static void PushEntryWithUID(std::vector<std::string> &Signers, char * const buf Signers.push_back(msg); } string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, - std::string const &key, + vector<string> const &keyFpts, + vector<string> const &keyFiles, vector<string> &GoodSigners, vector<string> &BadSigners, vector<string> &WorthlessSigners, @@ -158,7 +161,6 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, std::clog << "inside VerifyGetSigners" << std::endl; int fd[2]; - bool const keyIsID = (key.empty() == false && key[0] != '/'); if (pipe(fd) < 0) return "Couldn't create pipe"; @@ -167,7 +169,15 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, if (pid < 0) return string("Couldn't spawn new process") + strerror(errno); else if (pid == 0) - ExecGPGV(outfile, file, 3, fd, (keyIsID ? "" : key)); + { + std::ostringstream keys; + if (keyFiles.empty() == false) + { + std::copy(keyFiles.begin(), keyFiles.end()-1, std::ostream_iterator<std::string>(keys, ",")); + keys << *keyFiles.rbegin(); + } + ExecGPGV(outfile, file, 3, fd, keys.str()); + } close(fd[1]); FILE *pipein = fdopen(fd[0], "r"); @@ -175,6 +185,7 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, // 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; char *buffer = NULL; bool gotNODATA = false; @@ -242,6 +253,9 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, } ValidSigners.push_back(sig); + + if (tokens.size() > 9 && sig != tokens[9]) + SubKeyMapping[tokens[9]].emplace_back(sig); } else if (strncmp(buffer, APTKEYWARNING, sizeof(APTKEYWARNING)-1) == 0) Warning("%s", buffer + sizeof(APTKEYWARNING)); @@ -254,26 +268,52 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, // 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 - if (keyIsID == true) + if (keyFpts.empty() == false) { if (Debug == true) - std::clog << "GoodSigs needs to be limited to keyid(s) " << key << std::endl; - auto const limitedTo = VectorizeString(key, ','); + { + std::clog << "GoodSigs needs to be limited to keyid(s): "; + std::copy(keyFpts.begin(), keyFpts.end(), std::ostream_iterator<std::string>(std::clog, ", ")); + std::clog << "\n"; + } std::vector<std::string> filteredGood; for (auto &&good: GoodSigners) { if (Debug == true) std::clog << "Key " << good << " is good sig, is it also a valid and allowed one? "; bool found = false; - for (auto const &l : limitedTo) + for (auto l : keyFpts) { - if (IsTheSameKey(l, good) == false) - continue; - // GOODSIG might be "just" a longid, so we check VALIDSIG which is always a fingerprint - if (std::find(ValidSigners.begin(), ValidSigners.end(), l) == ValidSigners.end()) - continue; - found = true; - break; + bool exactKey = false; + if (APT::String::Endswith(l, "!")) + { + exactKey = true; + l.erase(l.length() - 1); + } + 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()) + continue; + found = true; + break; + } + else if (exactKey == false) + { + auto const master = SubKeyMapping.find(l); + if (master == SubKeyMapping.end()) + continue; + for (auto const &sub : master->second) + if (IsTheSameKey(sub, good)) + { + if (std::find(ValidSigners.cbegin(), ValidSigners.cend(), sub) == ValidSigners.cend()) + continue; + found = true; + break; + } + if (found) + break; + } } if (Debug) std::clog << (found ? "yes" : "no") << "\n"; @@ -325,7 +365,7 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, } else if (WEXITSTATUS(status) == 0) { - if (keyIsID) + if (keyFpts.empty() == false) { // 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 @@ -351,7 +391,6 @@ 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 - std::string const key = LookupTag(Message, "Signed-By"); vector<string> GoodSigners; vector<string> BadSigners; // a worthless signature is a expired or revoked one @@ -363,8 +402,15 @@ bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm) Res.Filename = Itm->DestFile; URIStart(Res); + std::vector<std::string> keyFpts, keyFiles; + for (auto &&key : VectorizeString(LookupTag(Message, "Signed-By"), ',')) + 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 - string const msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), key, + string const msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), keyFpts, keyFiles, GoodSigners, BadSigners, WorthlessSigners, SoonWorthlessSigners, NoPubKeySigners); if (_error->PendingError()) diff --git a/test/integration/framework b/test/integration/framework index b0456096c..8ec2e80cf 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -1988,6 +1988,7 @@ mapkeynametokeyid() { *Joe*|*Sixpack*|newarchive) echo '5A90D141DBAC8DAE';; *Rex*|*Expired*) echo '4BC0A39C27CE74F9';; *Marvin*|*Paranoid*) echo 'E8525D47528144E2';; + *Sebastian*|*Subkey*) echo '5B6896415D44C43E';; oldarchive) echo 'FDD2DB85F68C85A3';; *) echo 'UNKNOWN KEY';; esac diff --git a/test/integration/sebastiansubkey.master.sec b/test/integration/sebastiansubkey.master.sec Binary files differnew file mode 100644 index 000000000..4d86fb983 --- /dev/null +++ b/test/integration/sebastiansubkey.master.sec diff --git a/test/integration/sebastiansubkey.pub b/test/integration/sebastiansubkey.pub Binary files differnew file mode 100644 index 000000000..c5f198c77 --- /dev/null +++ b/test/integration/sebastiansubkey.pub diff --git a/test/integration/sebastiansubkey.sec b/test/integration/sebastiansubkey.sec Binary files differnew file mode 100644 index 000000000..fd40889da --- /dev/null +++ b/test/integration/sebastiansubkey.sec diff --git a/test/integration/test-apt-key b/test/integration/test-apt-key index d690a9026..a1e633ca3 100755 --- a/test/integration/test-apt-key +++ b/test/integration/test-apt-key @@ -89,6 +89,14 @@ gpg: unchanged: 1' aptkey --fakeroot update testsuccess test -s "${TMPWORKINGDIRECTORY}/aptkey.export" testsuccess test -s "${TMPWORKINGDIRECTORY}/aptkey.exportall" + msgtest 'Check that multiple keys can be' 'exported' + aptkey export 'Sixpack' 'Expired' > "${TMPWORKINGDIRECTORY}/aptkey.export" 2>/dev/null + aptkey --keyring "${KEYDIR}/rexexpired.pub.${EXT}" \ + --keyring "${ROOTDIR}/etc/apt/trusted.gpg.d/joesixpack.${EXT}" exportall > "${TMPWORKINGDIRECTORY}/aptkey.exportall" + testsuccess --nomsg cmp "${TMPWORKINGDIRECTORY}/aptkey.export" "${TMPWORKINGDIRECTORY}/aptkey.exportall" + testsuccess test -s "${TMPWORKINGDIRECTORY}/aptkey.export" + testsuccess test -s "${TMPWORKINGDIRECTORY}/aptkey.exportall" + msgtest 'Execute update again to trigger removal of' 'Rex Expired key' ${TESTSTATE} --nomsg aptkey --fakeroot update @@ -274,6 +282,14 @@ gpg: unchanged: 1' aptkey --fakeroot update msgtest 'Test verify a file' 'with good keyring' testsuccess --nomsg aptkey --quiet --readonly --keyring "${KEYDIR}/testcase-multikey.pub.${EXT}" verify "${SIGNATURE}.gpg" "${SIGNATURE}" + msgtest 'Test verify a file' 'with good keyrings 1' + testsuccess --nomsg aptkey --quiet --readonly --keyring "${KEYDIR}/joesixpack.pub.${EXT}" \ + --keyring "${KEYDIR}/marvinparanoid.pub.${EXT}" verify "${SIGNATURE}.gpg" "${SIGNATURE}" + + msgtest 'Test verify a file' 'with good keyrings 2' + testsuccess --nomsg aptkey --quiet --readonly --keyring "${KEYDIR}/marvinparanoid.pub.${EXT}" \ + --keyring "${KEYDIR}/joesixpack.pub.${EXT}" verify "${SIGNATURE}.gpg" "${SIGNATURE}" + msgtest 'Test fail verify a file' 'with bad keyring' testfailure --nomsg aptkey --quiet --readonly --keyring "${KEYDIR}/joesixpack.pub.${EXT}" verify "${SIGNATURE}.gpg" "${SIGNATURE}" diff --git a/test/integration/test-method-gpgv b/test/integration/test-method-gpgv index 5e00b1f13..b7cf11bdc 100755 --- a/test/integration/test-method-gpgv +++ b/test/integration/test-method-gpgv @@ -40,6 +40,11 @@ testrun() { testgpgv 'Good signed with fingerprint' 'Good: GOODSIG 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> +[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> +[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> [GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2016-09-01 1472742625 0 4 0 1 1 00 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE' testsuccess grep '^\s\+Good:\s\+$' method.output @@ -87,7 +92,21 @@ Config-Item: APT::Hashes::SHA1::Weak=true 600 URI Acquire URI: file:///dev/null Filename: /dev/zero -Signed-By: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE +Signed-By: /dev/null,34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE +' | runapt "${METHODSDIR}/gpgv" +} +testrun + +gpgvmethod() { + echo '601 Configuration +Config-Item: Debug::Acquire::gpgv=1 +Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Hashes::SHA1::Weak=true + +600 URI Acquire +URI: file:///dev/null +Filename: /dev/zero +Signed-By: 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE,/dev/null ' | runapt "${METHODSDIR}/gpgv" } testrun @@ -96,7 +115,33 @@ testgpgv 'Good signed with long keyid but not signed-by key' 'NoPubKey: GOODSIG [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' '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 + +gpgvmethod() { + echo '601 Configuration +Config-Item: Debug::Acquire::gpgv=1 +Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Hashes::SHA1::Weak=true + +600 URI Acquire +URI: file:///dev/null +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> +[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> +[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> +[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> +[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 diff --git a/test/integration/test-releasefile-verification b/test/integration/test-releasefile-verification index 36a90f9d5..382d89ecd 100755 --- a/test/integration/test-releasefile-verification +++ b/test/integration/test-releasefile-verification @@ -233,22 +233,39 @@ runtest() { " aptcache show apt installaptnew - msgmsg 'Cold archive signed by good keyring' 'Marvin Paranoid' - prepare "${PKGFILE}" + msgmsg 'Cold archive signed by bad keyring' 'Joe Sixpack' rm -rf rootdir/var/lib/apt/lists - signreleasefiles 'Marvin Paranoid' 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' + + msgmsg 'Cold archive signed by good keyring' 'Marvin Paranoid' + prepare "${PKGFILE}" + signreleasefiles 'Marvin Paranoid' + rm -rf rootdir/var/lib/apt/lists successfulaptgetupdate testsuccessequal "$(cat "${PKGFILE}") " aptcache show apt installaptold - msgmsg 'Cold archive signed by bad keyring' 'Joe Sixpack' + msgmsg 'Cold archive signed by good keyrings' 'Marvin Paranoid, Joe Sixpack' rm -rf rootdir/var/lib/apt/lists - signreleasefiles 'Joe Sixpack' - updatewithwarnings '^W: .* NO_PUBKEY' - sed -i "s#^\(deb\(-src\)\?\) \[signed-by=$MARVIN\] #\1 #" rootdir/etc/apt/sources.list.d/* + local SIXPACK="$(readlink -f keys/joesixpack.pub)" + sed -i "s# \[signed-by=[^]]\+\] # [signed-by=$MARVIN,$SIXPACK] #" rootdir/etc/apt/sources.list.d/* + successfulaptgetupdate + testsuccessequal "$(cat "${PKGFILE}") +" aptcache show apt + installaptold + + msgmsg 'Cold archive signed by good keyrings' 'Joe Sixpack, Marvin Paranoid' + rm -rf rootdir/var/lib/apt/lists + local SIXPACK="$(readlink -f keys/joesixpack.pub)" + sed -i "s# \[signed-by=[^]]\+\] # [signed-by=$SIXPACK,$MARVIN] #" rootdir/etc/apt/sources.list.d/* + successfulaptgetupdate + testsuccessequal "$(cat "${PKGFILE}") +" aptcache show apt + installaptold + sed -i "s# \[signed-by=[^]]\+\] # #" rootdir/etc/apt/sources.list.d/* local MARVIN="$(aptkey --keyring $MARVIN finger --with-colons | grep '^fpr' | cut -d':' -f 10)" msgmsg 'Cold archive signed by bad keyid' 'Joe Sixpack' @@ -342,6 +359,44 @@ Signed-By: ${MARVIN} ${MARVIN}, \\ testsuccessequal "$(cat "${PKGFILE}-new") " aptcache show apt installaptnew + + cp -a keys/sebastiansubkey.pub rootdir/etc/apt/trusted.gpg.d/sebastiansubkey.gpg + local SEBASTIAN="$(aptkey --keyring keys/sebastiansubkey.pub finger --with-colons | grep -m 1 '^fpr' | cut -d':' -f 10)" + msgmsg 'Warm archive with subkey signing' 'Sebastian Subkey' + rm -rf rootdir/var/lib/apt/lists + cp -a rootdir/var/lib/apt/lists-bak rootdir/var/lib/apt/lists + signreleasefiles 'Sebastian Subkey' + 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 + successfulaptgetupdate + testsuccessequal "$(cat "${PKGFILE}-new") +" aptcache show apt + installaptnew + + msgmsg 'Warm archive with wrong 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\ +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' + testsuccessequal "$(cat "${PKGFILE}") +" aptcache show apt + installaptold + + local SUBKEY="$(aptkey --keyring keys/sebastiansubkey.pub finger --with-colons | grep -m 2 '^fpr' | tail -n -1 | cut -d':' -f 10)" + 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\ +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") +" aptcache show apt + installaptnew + rm -f rootdir/etc/apt/trusted.gpg.d/sebastiansubkey.gpg } runtest2() { diff --git a/test/integration/test-signed-by-option b/test/integration/test-signed-by-option index 4ab2e28bb..faa7dec44 100755 --- a/test/integration/test-signed-by-option +++ b/test/integration/test-signed-by-option @@ -7,7 +7,27 @@ TESTDIR="$(readlink -f "$(dirname "$0")")" setupenvironment configarchitecture 'amd64' -msgtest "Check that a repository with signed-by and two components works" +msgtest 'Check that a repository with' 'signed-by and two components works' echo 'deb [signed-by=CDE5618B8805FD6E202CE9C2D73C39E56580B386] https://people.debian.org/~jak/debian/ stable main contrib # Äffchen' > rootdir/etc/apt/sources.list +testsuccess --nomsg aptcache policy + +msgtest 'Check that a repository with' 'two fingerprints work' +echo 'deb [signed-by=CDE5618B8805FD6E202CE9C2D73C39E56580B386,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] https://people.debian.org/~jak/debian/ stable main contrib # Äffchen' > rootdir/etc/apt/sources.list +testsuccess --nomsg aptcache policy + +msgtest 'Check that a repository with' 'exact fingerprint works' +echo 'deb [signed-by=CDE5618B8805FD6E202CE9C2D73C39E56580B386!] https://people.debian.org/~jak/debian/ stable main contrib # Äffchen' > rootdir/etc/apt/sources.list +testsuccess --nomsg aptcache policy +msgtest 'Check that a repository with' 'whitespaced fingerprints work' +echo 'deb [signed-by=CDE5618B8805FD6E202CE9C2D73C39E56580B386!,,,,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] https://people.debian.org/~jak/debian/ stable main contrib # Äffchen' > rootdir/etc/apt/sources.list +cat > rootdir/etc/apt/sources.list.d/people.sources <<EOF +Types: deb +URIs: mirror+file:/var/lib/apt/mirror.lst +Suites: stable testing +Components: main contrib +Architectures: amd64 i386 +Signed-By: CDE5618B8805FD6E202CE9C2D73C39E56580B386! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + , , BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +EOF testsuccess --nomsg aptcache policy |