summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@debian.org>2018-10-14 19:23:41 +0000
committerJulian Andres Klode <jak@debian.org>2018-10-14 19:23:41 +0000
commitb80e48783c183aeaf1d30d898a7743f091d96336 (patch)
tree7a0e3711dd68bbd8fdfd0d07f9af6f33aa9d2d51
parentbb2f6c8c2a965ac1ff01582b93e64da8991dcbfc (diff)
parent8375d5b58038fc026098dcccc3de87cd9d740334 (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.cc11
-rw-r--r--apt-pkg/deb/debmetaindex.cc113
-rw-r--r--cmdline/apt-key.in157
-rw-r--r--doc/sources.list.5.xml29
-rw-r--r--methods/gpgv.cc82
-rw-r--r--test/integration/framework1
-rw-r--r--test/integration/sebastiansubkey.master.secbin0 -> 4829 bytes
-rw-r--r--test/integration/sebastiansubkey.pubbin0 -> 2567 bytes
-rw-r--r--test/integration/sebastiansubkey.secbin0 -> 3546 bytes
-rwxr-xr-xtest/integration/test-apt-key16
-rwxr-xr-xtest/integration/test-method-gpgv49
-rwxr-xr-xtest/integration/test-releasefile-verification69
-rwxr-xr-xtest/integration/test-signed-by-option22
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
new file mode 100644
index 000000000..4d86fb983
--- /dev/null
+++ b/test/integration/sebastiansubkey.master.sec
Binary files differ
diff --git a/test/integration/sebastiansubkey.pub b/test/integration/sebastiansubkey.pub
new file mode 100644
index 000000000..c5f198c77
--- /dev/null
+++ b/test/integration/sebastiansubkey.pub
Binary files differ
diff --git a/test/integration/sebastiansubkey.sec b/test/integration/sebastiansubkey.sec
new file mode 100644
index 000000000..fd40889da
--- /dev/null
+++ b/test/integration/sebastiansubkey.sec
Binary files differ
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