Browse Source

Support subkeys properly in Signed-By options

If we limit a file to be signed by a certain key it should usually
accept also being signed by any of this keys subkeys instead of
requiring each subkey to be listed explicitly. If the later is really
wanted we support now also the same syntax as gpg does with appending an
exclamation mark at the end of the fingerprint to force no mapping.
tags/debian/1.8.0_alpha1
David Kalnischkies 2 years ago
parent
commit
ff8fa4ab4b
9 changed files with 190 additions and 61 deletions
  1. +62
    -51
      apt-pkg/deb/debmetaindex.cc
  2. +36
    -8
      methods/gpgv.cc
  3. +1
    -0
      test/integration/framework
  4. BIN
      test/integration/sebastiansubkey.master.sec
  5. BIN
      test/integration/sebastiansubkey.pub
  6. BIN
      test/integration/sebastiansubkey.sec
  7. +32
    -1
      test/integration/test-method-gpgv
  8. +38
    -0
      test/integration/test-releasefile-verification
  9. +21
    -1
      test/integration/test-signed-by-option

+ 62
- 51
apt-pkg/deb/debmetaindex.cc View File

@@ -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;
}


+ 36
- 8
methods/gpgv.cc View File

@@ -20,6 +20,7 @@
#include <array>
#include <iostream>
#include <iterator>
#include <map>
#include <sstream>
#include <string>
#include <vector>
@@ -175,6 +176,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 +244,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));
@@ -265,15 +270,38 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
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 : limitedTo)
{
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";


+ 1
- 0
test/integration/framework View File

@@ -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


BIN
test/integration/sebastiansubkey.master.sec View File


BIN
test/integration/sebastiansubkey.pub View File


BIN
test/integration/sebastiansubkey.sec View File


+ 32
- 1
test/integration/test-method-gpgv View File

@@ -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
@@ -96,7 +101,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

+ 38
- 0
test/integration/test-releasefile-verification View File

@@ -342,6 +342,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() {


+ 21
- 1
test/integration/test-signed-by-option View File

@@ -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

Loading…
Cancel
Save