Skip to content
This repository has been archived by the owner on Dec 12, 2022. It is now read-only.

Commit

Permalink
implement Signed-By option for sources.list
Browse files Browse the repository at this point in the history
Limits which key(s) can be used to sign a repository. Not immensely useful
from a security perspective all by itself, but if the user has
additional measures in place to confine a repository (like pinning) an
attacker who gets the key for such a repository is limited to its
potential and can't use the key to sign its attacks for an other (maybe
less limited) repository… (yes, this is as weak as it sounds, but having
the capability might come in handy for implementing other stuff later).
  • Loading branch information
DonKult committed Aug 10, 2015
1 parent 0741dae commit b0d4085
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 56 deletions.
15 changes: 14 additions & 1 deletion apt-pkg/acquire-item.cc
Expand Up @@ -808,7 +808,6 @@ string pkgAcqMetaBase::Custom600Headers() const
Header += MaximumSize;

string const FinalFile = GetFinalFilename();

struct stat Buf;
if (stat(FinalFile.c_str(),&Buf) == 0)
Header += "\nLast-Modified: " + TimeRFC1123(Buf.st_mtime);
Expand Down Expand Up @@ -1132,6 +1131,10 @@ string pkgAcqMetaClearSig::Custom600Headers() const
{
string Header = pkgAcqMetaBase::Custom600Headers();
Header += "\nFail-Ignore: true";
std::string const key = TransactionManager->MetaIndexParser->GetSignedBy();
if (key.empty() == false)
Header += "\nSigned-By: " + key;

return Header;
}
/*}}}*/
Expand Down Expand Up @@ -1372,6 +1375,16 @@ pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire * const Owner,
/*}}}*/
pkgAcqMetaSig::~pkgAcqMetaSig() /*{{{*/
{
}
/*}}}*/
// pkgAcqMetaSig::Custom600Headers - Insert custom request headers /*{{{*/
std::string pkgAcqMetaSig::Custom600Headers() const
{
std::string Header = pkgAcqTransactionItem::Custom600Headers();
std::string const key = TransactionManager->MetaIndexParser->GetSignedBy();
if (key.empty() == false)
Header += "\nSigned-By: " + key;
return Header;
}
/*}}}*/
// AcqMetaSig::Done - The signature was downloaded/verified /*{{{*/
Expand Down
1 change: 1 addition & 0 deletions apt-pkg/acquire-item.h
Expand Up @@ -541,6 +541,7 @@ class APT_HIDDEN pkgAcqMetaSig : public pkgAcqTransactionItem
virtual void Failed(std::string const &Message,pkgAcquire::MethodConfig const * const Cnf);
virtual void Done(std::string const &Message, HashStringList const &Hashes,
pkgAcquire::MethodConfig const * const Cnf);
virtual std::string Custom600Headers() const;

/** \brief Create a new pkgAcqMetaSig. */
pkgAcqMetaSig(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager,
Expand Down
17 changes: 16 additions & 1 deletion apt-pkg/contrib/gpgv.cc
Expand Up @@ -16,6 +16,8 @@
#include <sys/wait.h>
#include <unistd.h>
#include <stddef.h>

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
Expand All @@ -42,7 +44,7 @@ static char * GenerateTemporaryFileTemplate(const char *basename) /*{{{*/
of the lifting in regards to merging keyrings. Fun for the whole family.
*/
void ExecGPGV(std::string const &File, std::string const &FileGPG,
int const &statusfd, int fd[2])
int const &statusfd, int fd[2], std::string const &key)
{
#define EINTERNAL 111
std::string const aptkey = _config->FindFile("Dir::Bin::apt-key", "/usr/bin/apt-key");
Expand All @@ -55,6 +57,19 @@ 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)
{
if (key[0] == '/')
{
Args.push_back("--keyring");
Args.push_back(key.c_str());
}
else
{
Args.push_back("--keyid");
Args.push_back(key.c_str());
}
}
Args.push_back("verify");

char statusfdstr[10];
Expand Down
5 changes: 4 additions & 1 deletion apt-pkg/contrib/gpgv.h
Expand Up @@ -38,9 +38,12 @@ class FileFd;
*
* @param File is the message (unsigned or clear-signed)
* @param FileSig is the signature (detached or clear-signed)
* @param statusfd is the fd given to gpgv as --status-fd
* @param fd is used as a pipe for the standard output of gpgv
* @param key is the specific one to be used instead of using all
*/
void ExecGPGV(std::string const &File, std::string const &FileSig,
int const &statusfd, int fd[2]) APT_NORETURN;
int const &statusfd, int fd[2], std::string const &Key = "") APT_NORETURN;
inline APT_NORETURN void ExecGPGV(std::string const &File, std::string const &FileSig,
int const &statusfd = -1) {
int fd[2];
Expand Down
35 changes: 35 additions & 0 deletions apt-pkg/deb/debmetaindex.cc
Expand Up @@ -461,6 +461,29 @@ bool debReleaseIndex::SetValidUntilMax(time_t const Valid)
else if (d->ValidUntilMax != Valid)
return _error->Error(_("Conflicting values set for option %s concerning source %s %s"), "Max-ValidTime", URI.c_str(), Dist.c_str());
return true;
}
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
; // 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.
std::string finger = pSignedBy;
finger.erase(std::remove(finger.begin(), finger.end(), ' '), finger.end());
std::transform(finger.begin(), finger.end(), finger.begin(), ::toupper);
if (finger.length() != 40 || finger.find_first_not_of("0123456789ABCDEF") != std::string::npos)
return _error->Error(_("Invalid value set for option %s concerning source %s %s (%s)"), "Signed-By", URI.c_str(), Dist.c_str(), "not a fingerprint");
}
SignedBy = pSignedBy;
}
else if (SignedBy != pSignedBy)
return _error->Error(_("Conflicting values set for option %s concerning source %s %s"), "Signed-By", URI.c_str(), Dist.c_str());
return true;
}
/*}}}*/
// ReleaseIndex::IsTrusted /*{{{*/
Expand Down Expand Up @@ -706,6 +729,18 @@ class APT_HIDDEN debSLTypeDebian : public pkgSourceList::Type /*{{{*/
Deb->SetValidUntilMin(GetTimeOption(Options, "valid-until-min")) == false)
return false;

std::map<std::string, std::string>::const_iterator const signedby = Options.find("signed-by");
if (signedby == Options.end())
{
if (Deb->SetSignedBy("") == false)
return false;
}
else
{
if (Deb->SetSignedBy(signedby->second) == false)
return false;
}

return true;
}

Expand Down
1 change: 1 addition & 0 deletions apt-pkg/deb/debmetaindex.h
Expand Up @@ -56,6 +56,7 @@ class APT_HIDDEN debReleaseIndex : public metaIndex
bool SetCheckValidUntil(TriState const Trusted);
bool SetValidUntilMin(time_t const Valid);
bool SetValidUntilMax(time_t const Valid);
bool SetSignedBy(std::string const &SignedBy);

virtual bool IsTrusted() const;

Expand Down
4 changes: 2 additions & 2 deletions apt-pkg/metaindex.cc
Expand Up @@ -27,8 +27,7 @@ bool metaIndex::Merge(pkgCacheGenerator &Gen,OpProgress *) const
metaIndex::metaIndex(std::string const &URI, std::string const &Dist,
char const * const Type)
: d(NULL), Indexes(NULL), Type(Type), URI(URI), Dist(Dist), Trusted(TRI_UNSET),
LoadedSuccessfully(TRI_UNSET),
Date(0), ValidUntil(0), SupportsAcquireByHash(false)
Date(0), ValidUntil(0), SupportsAcquireByHash(false), LoadedSuccessfully(TRI_UNSET)
{
/* nothing */
}
Expand All @@ -48,6 +47,7 @@ APT_PURE std::string metaIndex::GetURI() const { return URI; }
APT_PURE std::string metaIndex::GetDist() const { return Dist; }
APT_PURE const char* metaIndex::GetType() const { return Type; }
APT_PURE metaIndex::TriState metaIndex::GetTrusted() const { return Trusted; }
APT_PURE std::string metaIndex::GetSignedBy() const { return SignedBy; }
APT_PURE std::string metaIndex::GetCodename() const { return Codename; }
APT_PURE std::string metaIndex::GetSuite() const { return Suite; }
APT_PURE bool metaIndex::GetSupportsAcquireByHash() const { return SupportsAcquireByHash; }
Expand Down
4 changes: 3 additions & 1 deletion apt-pkg/metaindex.h
Expand Up @@ -52,7 +52,7 @@ class metaIndex
std::string URI;
std::string Dist;
TriState Trusted;
TriState LoadedSuccessfully;
std::string SignedBy;

// parsed from a file
std::string Suite;
Expand All @@ -61,13 +61,15 @@ class metaIndex
time_t ValidUntil;
bool SupportsAcquireByHash;
std::map<std::string, checkSum *> Entries;
TriState LoadedSuccessfully;

public:
// Various accessors
std::string GetURI() const;
std::string GetDist() const;
const char* GetType() const;
TriState GetTrusted() const;
std::string GetSignedBy() const;

std::string GetCodename() const;
std::string GetSuite() const;
Expand Down
28 changes: 15 additions & 13 deletions apt-pkg/sourcelist.cc
Expand Up @@ -93,27 +93,29 @@ bool pkgSourceList::Type::ParseStanza(vector<metaIndex *> &List, /*{{{*/
if (Enabled.empty() == false && StringToBool(Enabled) == false)
return true;

std::map<char const * const, char const * const> mapping;
std::map<char const * const, std::pair<char const * const, bool> > mapping;
#define APT_PLUSMINUS(X, Y) \
mapping.insert(std::make_pair(X, Y)); \
mapping.insert(std::make_pair(X "Add", Y "+")); \
mapping.insert(std::make_pair(X "Remove", Y "-"))
mapping.insert(std::make_pair(X, std::make_pair(Y, true))); \
mapping.insert(std::make_pair(X "Add", std::make_pair(Y "+", true))); \
mapping.insert(std::make_pair(X "Remove", std::make_pair(Y "-", true)))
APT_PLUSMINUS("Architectures", "arch");
APT_PLUSMINUS("Languages", "lang");
APT_PLUSMINUS("Targets", "target");
#undef APT_PLUSMINUS
mapping.insert(std::make_pair("Trusted", "trusted"));
mapping.insert(std::make_pair("Check-Valid-Until", "check-valid-until"));
mapping.insert(std::make_pair("Valid-Until-Min", "valid-until-min"));
mapping.insert(std::make_pair("Valid-Until-Max", "valid-until-max"));
mapping.insert(std::make_pair("Trusted", std::make_pair("trusted", false)));
mapping.insert(std::make_pair("Check-Valid-Until", std::make_pair("check-valid-until", false)));
mapping.insert(std::make_pair("Valid-Until-Min", std::make_pair("valid-until-min", false)));
mapping.insert(std::make_pair("Valid-Until-Max", std::make_pair("valid-until-max", false)));
mapping.insert(std::make_pair("Signed-By", std::make_pair("signed-by", false)));

for (std::map<char const * const, char const * const>::const_iterator m = mapping.begin(); m != mapping.end(); ++m)
for (std::map<char const * const, std::pair<char const * const, bool> >::const_iterator m = mapping.begin(); m != mapping.end(); ++m)
if (Tags.Exists(m->first))
{
// for deb822 the " " is the delimiter, but the backend expects ","
std::string option = Tags.FindS(m->first);
std::replace(option.begin(), option.end(), ' ', ',');
Options[m->second] = option;
std::string option = Tags.FindS(m->first);
// for deb822 the " " is the delimiter, but the backend expects ","
if (m->second.second == true)
std::replace(option.begin(), option.end(), ' ', ',');
Options[m->second.first] = option;
}

// now create one item per suite/section
Expand Down
22 changes: 19 additions & 3 deletions cmdline/apt-key.in
Expand Up @@ -199,7 +199,7 @@ remove_key_from_keyring() {
foreach_keyring_do() {
local ACTION="$1"
shift
# if a --keyring was given, just remove from there
# if a --keyring was given, just work on this one
if [ -n "$FORCED_KEYRING" ]; then
$ACTION "$FORCED_KEYRING" "$@"
else
Expand Down Expand Up @@ -279,7 +279,14 @@ merge_back_changes() {
}

setup_merged_keyring() {
if [ -z "$FORCED_KEYRING" ]; then
if [ -n "$FORCED_KEYID" ]; then
foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/allrings.gpg"
FORCED_KEYRING="${GPGHOMEDIR}/forcedkeyid.gpg"
TRUSTEDFILE="${FORCED_KEYRING}"
GPG="$GPG --keyring $TRUSTEDFILE"
# ignore error as this "just" means we haven't found the forced keyid and the keyring will be empty
$GPG_CMD --batch --yes --keyring "${GPGHOMEDIR}/allrings.gpg" --export "$FORCED_KEYID" | $GPG --batch --yes --import || true
elif [ -z "$FORCED_KEYRING" ]; then
foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg"
if [ -r "${GPGHOMEDIR}/pubring.gpg" ]; then
cp -a "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg"
Expand Down Expand Up @@ -328,12 +335,17 @@ while [ -n "$1" ]; do
TRUSTEDFILE="$1"
FORCED_KEYRING="$1"
;;
--keyid)
shift
FORCED_KEYID="$1"
;;
--secret-keyring)
shift
FORCED_SECRET_KEYRING="$1"
;;
--readonly)
merge_back_changes() { true; }
create_new_keyring() { true; }
;;
--fakeroot)
requires_root() { true; }
Expand Down Expand Up @@ -460,7 +472,11 @@ case "$command" in
verify)
setup_merged_keyring
if which gpgv >/dev/null 2>&1; then
gpgv --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@"
if [ -n "$FORCED_KEYRING" ]; then
gpgv --homedir "${GPGHOMEDIR}" --keyring "${FORCED_KEYRING}" --ignore-time-conflict "$@"
else
gpgv --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@"
fi
else
$GPG --verify "$@"
fi
Expand Down
24 changes: 12 additions & 12 deletions doc/sources.list.5.xml
Expand Up @@ -232,18 +232,18 @@ deb-src [ option1=value1 option2=value2 ] uri suite [component1] [component2] [.
anomalies.

<itemizedlist>
<listitem><para><option>Trusted</option> (<option>trusted</option>)
is a tri-state value which defaults to APT deciding if a source
is considered trusted or if warnings should be raised before e.g.
packages are installed from this source. This option can be used
to override this decision either with the value <literal>yes</literal>,
which lets APT consider this source always as a trusted source
even if it has no or fails authentication checks by disabling parts
of &apt-secure; and should therefore only be used in a local and trusted
context (if at all) as otherwise security is breached. The opposite
can be achieved with the value no, which causes the source to be handled
as untrusted even if the authentication checks passed successfully.
The default value can't be set explicitly.
<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 a
fingerprint of a key in either the
<filename>trusted.gpg</filename> keyring or in one of 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 key with this
fingerprint is used for the &apt-secure; verification of this
repository. Otherwise all keys in the trusted keyrings are
considered valid signers for this repository.
</para></listitem>

<listitem><para><option>Check-Valid-Until</option> (<option>check-valid-until</option>)
Expand Down
18 changes: 10 additions & 8 deletions methods/gpgv.cc
Expand Up @@ -37,13 +37,14 @@ class GPGVMethod : public pkgAcqMethod
{
private:
string VerifyGetSigners(const char *file, const char *outfile,
vector<string> &GoodSigners,
std::string const &key,
vector<string> &GoodSigners,
vector<string> &BadSigners,
vector<string> &WorthlessSigners,
vector<string> &NoPubKeySigners);

protected:
virtual bool Fetch(FetchItem *Itm);
virtual bool URIAcquire(std::string const &Message, FetchItem *Itm);
virtual bool Configuration(string Message);
public:

Expand All @@ -61,6 +62,7 @@ bool GPGVMethod::Configuration(string Message)
}

string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
std::string const &key,
vector<string> &GoodSigners,
vector<string> &BadSigners,
vector<string> &WorthlessSigners,
Expand All @@ -80,7 +82,7 @@ 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);
ExecGPGV(outfile, file, 3, fd, key);
close(fd[1]);

FILE *pipein = fdopen(fd[0], "r");
Expand Down Expand Up @@ -174,11 +176,11 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
return _("Unknown error executing apt-key");
}

bool GPGVMethod::Fetch(FetchItem *Itm)
bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
{
URI Get = Itm->Uri;
string Path = Get.Host + Get.Path; // To account for relative paths
string keyID;
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
Expand All @@ -190,7 +192,7 @@ bool GPGVMethod::Fetch(FetchItem *Itm)
URIStart(Res);

// Run apt-key on file, extract contents and get the key ID of the signer
string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(),
string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), key,
GoodSigners, BadSigners, WorthlessSigners,
NoPubKeySigners);
if (GoodSigners.empty() || !BadSigners.empty() || !NoPubKeySigners.empty())
Expand Down

0 comments on commit b0d4085

Please sign in to comment.