rpm/lib/rpmchecksig.c

467 lines
12 KiB
C

/** \ingroup rpmcli
* \file lib/rpmchecksig.c
* Verify the signature of a package.
*/
#include "system.h"
#include <rpm/rpmlib.h> /* RPMSIGTAG & related */
#include <rpm/rpmpgp.h>
#include <rpm/rpmcli.h>
#include <rpm/rpmfileutil.h> /* rpmMkTemp() */
#include <rpm/rpmdb.h>
#include <rpm/rpmts.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmstring.h>
#include <rpm/rpmkeyring.h>
#include "rpmio/digest.h"
#include "rpmio/rpmio_internal.h" /* fdSetBundle() */
#include "lib/rpmlead.h"
#include "lib/signature.h"
#include "debug.h"
int _print_pkts = 0;
static int doImport(rpmts ts, const char *fn, char *buf, ssize_t blen)
{
char const * const pgpmark = "-----BEGIN PGP ";
size_t marklen = strlen(pgpmark);
int res = 0;
int keyno = 1;
char *start = strstr(buf, pgpmark);
while (start) {
uint8_t *pkt = NULL;
size_t pktlen = 0;
/* Read pgp packet. */
if (pgpParsePkts(start, &pkt, &pktlen) == PGPARMOR_PUBKEY) {
/* Import pubkey packet(s). */
if (rpmtsImportPubkey(ts, pkt, pktlen) != RPMRC_OK) {
rpmlog(RPMLOG_ERR, _("%s: key %d import failed.\n"), fn, keyno);
res++;
}
} else {
rpmlog(RPMLOG_ERR, _("%s: key %d not an armored public key.\n"),
fn, keyno);
res++;
}
/* See if there are more keys in the buffer */
if (start + marklen < buf + blen) {
start = strstr(start + marklen, pgpmark);
} else {
start = NULL;
}
keyno++;
free(pkt);
}
return res;
}
int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv)
{
int res = 0;
for (ARGV_const_t arg = argv; arg && *arg; arg++) {
const char *fn = *arg;
uint8_t *buf = NULL;
ssize_t blen = 0;
char *t = NULL;
int iorc;
/* If arg looks like a keyid, then attempt keyserver retrieve. */
if (rstreqn(fn, "0x", 2)) {
const char * s = fn + 2;
int i;
for (i = 0; *s && isxdigit(*s); s++, i++)
{};
if (i == 8 || i == 16) {
t = rpmExpand("%{_hkp_keyserver_query}", fn+2, NULL);
if (t && *t != '%')
fn = t;
}
}
/* Read the file and try to import all contained keys */
iorc = rpmioSlurp(fn, &buf, &blen);
if (iorc || buf == NULL || blen < 64) {
rpmlog(RPMLOG_ERR, _("%s: import read failed(%d).\n"), fn, iorc);
res++;
} else {
res += doImport(ts, fn, (char *)buf, blen);
}
free(t);
free(buf);
}
return res;
}
/**
* @todo If the GPG key was known available, the md5 digest could be skipped.
*/
static int readFile(FD_t fd, const char * fn, pgpDig dig,
rpmDigestBundle plbundle, rpmDigestBundle hdrbundle)
{
unsigned char buf[4*BUFSIZ];
ssize_t count;
int rc = 1;
Header h = NULL;
/* Read the header from the package. */
if ((h = headerRead(fd, HEADER_MAGIC_YES)) == NULL) {
rpmlog(RPMLOG_ERR, _("%s: headerRead failed\n"), fn);
goto exit;
}
if (headerIsEntry(h, RPMTAG_HEADERIMMUTABLE)) {
struct rpmtd_s utd;
if (!headerGet(h, RPMTAG_HEADERIMMUTABLE, &utd, HEADERGET_DEFAULT)){
rpmlog(RPMLOG_ERR,
_("%s: Immutable header region could not be read. "
"Corrupted package?\n"), fn);
goto exit;
}
rpmDigestBundleUpdate(hdrbundle, rpm_header_magic, sizeof(rpm_header_magic));
rpmDigestBundleUpdate(hdrbundle, utd.data, utd.count);
rpmtdFreeData(&utd);
}
/* Read the payload from the package. */
while ((count = Fread(buf, sizeof(buf[0]), sizeof(buf), fd)) > 0) {}
if (count < 0) {
rpmlog(RPMLOG_ERR, _("%s: Fread failed: %s\n"), fn, Fstrerror(fd));
goto exit;
}
rc = 0;
exit:
headerFree(h);
return rc;
}
/* Parse the parameters from the OpenPGP packets that will be needed. */
/* XXX TODO: unify with similar parsePGP() in package.c */
static rpmRC parsePGP(rpmtd sigtd, const char *fn, pgpDig dig)
{
rpmRC rc = RPMRC_FAIL;
int debug = (_print_pkts & rpmIsDebug());
if ((pgpPrtPkts(sigtd->data, sigtd->count, dig, debug) == 0) &&
(dig->signature.version == 3 || dig->signature.version == 4)) {
rc = RPMRC_OK;
} else {
rpmlog(RPMLOG_ERR,
_("skipping package %s with unverifiable V%u signature\n"), fn,
dig->signature.version);
}
return rc;
}
/*
* Figure best available signature.
* XXX TODO: Similar detection in rpmReadPackageFile(), unify these.
*/
static rpmSigTag bestSig(Header sigh, int nosignatures, int nodigests)
{
rpmSigTag sigtag = 0;
if (sigtag == 0 && !nosignatures) {
if (headerIsEntry(sigh, RPMSIGTAG_DSA))
sigtag = RPMSIGTAG_DSA;
else if (headerIsEntry(sigh, RPMSIGTAG_RSA))
sigtag = RPMSIGTAG_RSA;
else if (headerIsEntry(sigh, RPMSIGTAG_GPG))
sigtag = RPMSIGTAG_GPG;
else if (headerIsEntry(sigh, RPMSIGTAG_PGP))
sigtag = RPMSIGTAG_PGP;
}
if (sigtag == 0 && !nodigests) {
if (headerIsEntry(sigh, RPMSIGTAG_MD5))
sigtag = RPMSIGTAG_MD5;
else if (headerIsEntry(sigh, RPMSIGTAG_SHA1))
sigtag = RPMSIGTAG_SHA1; /* XXX never happens */
}
return sigtag;
}
static const char *sigtagname(rpmSigTag sigtag, int upper)
{
const char *n = NULL;
switch (sigtag) {
case RPMSIGTAG_SIZE:
n = (upper ? "SIZE" : "size");
break;
case RPMSIGTAG_SHA1:
n = (upper ? "SHA1" : "sha1");
break;
case RPMSIGTAG_MD5:
n = (upper ? "MD5" : "md5");
break;
case RPMSIGTAG_RSA:
n = (upper ? "RSA" : "rsa");
break;
case RPMSIGTAG_PGP5: /* XXX legacy */
case RPMSIGTAG_PGP:
n = (upper ? "(MD5) PGP" : "(md5) pgp");
break;
case RPMSIGTAG_DSA:
n = (upper ? "(SHA1) DSA" : "(sha1) dsa");
break;
case RPMSIGTAG_GPG:
n = (upper ? "GPG" : "gpg");
break;
default:
n = (upper ? "?UnknownSigatureType?" : "???");
break;
}
return n;
}
/*
* Format sigcheck result for output, appending the message spew to buf and
* bad/missing keyids to keyprob.
*
* In verbose mode, just dump it all. Otherwise ok signatures
* are dumped lowercase, bad sigs uppercase and for PGP/GPG
* if misssing/untrusted key it's uppercase in parenthesis
* and stash the key id as <SIGTYPE>#<keyid>. Pfft.
*/
static void formatResult(rpmSigTag sigtag, rpmRC sigres, const char *result,
int havekey, char **keyprob, char **buf)
{
char *msg = NULL;
if (rpmIsVerbose()) {
rasprintf(&msg, " %s", result);
} else {
/* Check for missing / untrusted keys in result. */
const char *signame = sigtagname(sigtag, (sigres != RPMRC_OK));
if (havekey && (sigres == RPMRC_NOKEY || sigres == RPMRC_NOTTRUSTED)) {
const char *tempKey = strstr(result, "ey ID");
if (tempKey) {
char keyid[sizeof(pgpKeyID_t) + 1];
rstrlcpy(keyid, tempKey + 6, sizeof(keyid));
rstrscat(keyprob, " ", signame, "#", keyid, NULL);
}
}
rasprintf(&msg, (*keyprob ? "(%s) " : "%s "), signame);
}
rstrcat(buf, msg);
free(msg);
}
static int rpmpkgVerifySigs(rpmKeyring keyring, rpmQueryFlags flags,
FD_t fd, const char *fn)
{
char *buf = NULL;
char *missingKeys = NULL;
char *untrustedKeys = NULL;
struct rpmtd_s sigtd;
rpmTag sigtag;
pgpDig dig = NULL;
pgpDigParams sigp;
Header sigh = NULL;
HeaderIterator hi = NULL;
char * msg = NULL;
int res = 1; /* assume failure */
int xx;
rpmRC rc;
int failed = 0;
int nodigests = !(flags & VERIFY_DIGEST);
int nosignatures = !(flags & VERIFY_SIGNATURE);
rpmDigestBundle plbundle = rpmDigestBundleNew();
rpmDigestBundle hdrbundle = rpmDigestBundleNew();
rpmlead lead = rpmLeadNew();
if ((rc = rpmLeadRead(fd, lead)) == RPMRC_OK) {
const char *lmsg = NULL;
rc = rpmLeadCheck(lead, &lmsg);
if (rc != RPMRC_OK)
rpmlog(RPMLOG_ERR, "%s: %s\n", fn, lmsg);
}
lead = rpmLeadFree(lead);
if (rc != RPMRC_OK) {
goto exit;
}
rc = rpmReadSignature(fd, &sigh, RPMSIGTYPE_HEADERSIG, &msg);
switch (rc) {
default:
rpmlog(RPMLOG_ERR, _("%s: rpmReadSignature failed: %s"), fn,
(msg && *msg ? msg : "\n"));
msg = _free(msg);
goto exit;
break;
case RPMRC_OK:
if (sigh == NULL) {
rpmlog(RPMLOG_ERR, _("%s: No signature available\n"), fn);
goto exit;
}
break;
}
msg = _free(msg);
/* Grab a hint of what needs doing to avoid duplication. */
sigtag = bestSig(sigh, nosignatures, nodigests);
dig = pgpNewDig();
sigp = &dig->signature;
/* XXX RSA needs the hash_algo, so decode early. */
if (sigtag == RPMSIGTAG_RSA || sigtag == RPMSIGTAG_PGP ||
sigtag == RPMSIGTAG_DSA || sigtag == RPMSIGTAG_GPG) {
xx = headerGet(sigh, sigtag, &sigtd, HEADERGET_DEFAULT);
xx = pgpPrtPkts(sigtd.data, sigtd.count, dig, 0);
rpmtdFreeData(&sigtd);
/* XXX assume same hash_algo in header-only and header+payload */
rpmDigestBundleAdd(plbundle, sigp->hash_algo, RPMDIGEST_NONE);
rpmDigestBundleAdd(hdrbundle, sigp->hash_algo, RPMDIGEST_NONE);
}
if (headerIsEntry(sigh, RPMSIGTAG_PGP) ||
headerIsEntry(sigh, RPMSIGTAG_PGP5) ||
headerIsEntry(sigh, RPMSIGTAG_MD5)) {
rpmDigestBundleAdd(plbundle, PGPHASHALGO_MD5, RPMDIGEST_NONE);
}
if (headerIsEntry(sigh, RPMSIGTAG_GPG)) {
rpmDigestBundleAdd(plbundle, PGPHASHALGO_SHA1, RPMDIGEST_NONE);
}
/* always do sha1 hash of header */
rpmDigestBundleAdd(hdrbundle, PGPHASHALGO_SHA1, RPMDIGEST_NONE);
/* Read the file, generating digest(s) on the fly. */
fdSetBundle(fd, plbundle);
if (readFile(fd, fn, dig, plbundle, hdrbundle)) {
goto exit;
}
rasprintf(&buf, "%s:%c", fn, (rpmIsVerbose() ? '\n' : ' ') );
hi = headerInitIterator(sigh);
for (; headerNext(hi, &sigtd) != 0; rpmtdFreeData(&sigtd)) {
char *result = NULL;
int havekey = 0;
DIGEST_CTX ctx = NULL;
if (sigtd.data == NULL) /* XXX can't happen */
continue;
/* Clean up parameters from previous sigtag. */
pgpCleanDig(dig);
switch (sigtd.tag) {
case RPMSIGTAG_GPG:
case RPMSIGTAG_PGP5: /* XXX legacy */
case RPMSIGTAG_PGP:
havekey = 1;
case RPMSIGTAG_RSA:
case RPMSIGTAG_DSA:
if (nosignatures)
continue;
if (parsePGP(&sigtd, fn, dig) != RPMRC_OK) {
goto exit;
}
ctx = rpmDigestBundleDupCtx(havekey ? plbundle : hdrbundle,
dig->signature.hash_algo);
break;
case RPMSIGTAG_SHA1:
if (nodigests)
continue;
ctx = rpmDigestBundleDupCtx(hdrbundle, PGPHASHALGO_SHA1);
break;
case RPMSIGTAG_MD5:
if (nodigests)
continue;
ctx = rpmDigestBundleDupCtx(plbundle, PGPHASHALGO_MD5);
break;
default:
continue;
break;
}
rc = rpmVerifySignature(keyring, &sigtd, dig, ctx, &result);
rpmDigestFinal(ctx, NULL, NULL, 0);
formatResult(sigtd.tag, rc, result, havekey,
(rc == RPMRC_NOKEY ? &missingKeys : &untrustedKeys),
&buf);
free(result);
if (rc != RPMRC_OK) {
failed = 1;
}
}
res = failed;
if (rpmIsVerbose()) {
rpmlog(RPMLOG_NOTICE, "%s", buf);
} else {
const char *ok = (failed ? _("NOT OK") : _("OK"));
rpmlog(RPMLOG_NOTICE, "%s%s%s%s%s%s%s%s\n", buf, ok,
missingKeys ? _(" (MISSING KEYS:") : "",
missingKeys ? missingKeys : "",
missingKeys ? _(") ") : "",
untrustedKeys ? _(" (UNTRUSTED KEYS:") : "",
untrustedKeys ? untrustedKeys : "",
untrustedKeys ? _(")") : "");
}
free(missingKeys);
free(untrustedKeys);
exit:
free(buf);
rpmDigestBundleFree(hdrbundle);
rpmDigestBundleFree(plbundle);
fdSetBundle(fd, NULL); /* XXX avoid double-free from fd close */
sigh = rpmFreeSignature(sigh);
hi = headerFreeIterator(hi);
pgpFreeDig(dig);
return res;
}
/* Wrapper around rpmkVerifySigs to preserve API */
int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn)
{
int rc = 1; /* assume failure */
if (ts && qva && fd && fn) {
rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
rc = rpmpkgVerifySigs(keyring, qva->qva_flags, fd, fn);
rpmKeyringFree(keyring);
}
return rc;
}
int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
{
const char * arg;
int res = 0;
rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
rpmVerifyFlags verifyFlags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
verifyFlags &= ~rpmcliQueryFlags;
while ((arg = *argv++) != NULL) {
FD_t fd = Fopen(arg, "r.ufdio");
if (fd == NULL || Ferror(fd)) {
rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"),
arg, Fstrerror(fd));
res++;
} else if (rpmpkgVerifySigs(keyring, verifyFlags, fd, arg)) {
res++;
}
Fclose(fd);
rpmdbCheckSignals();
}
rpmKeyringFree(keyring);
return res;
}