rpm/lib/package.c

719 lines
18 KiB
C

/** \ingroup header
* \file lib/package.c
*/
#include "system.h"
#include <netinet/in.h>
#include <rpm/rpmlib.h> /* XXX RPMSIGTAG, other sig stuff */
#include <rpm/rpmts.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmstring.h>
#include <rpm/rpmkeyring.h>
#include "lib/rpmlead.h"
#include "lib/signature.h"
#include "rpmio/digest.h"
#include "rpmio/rpmio_internal.h" /* fd digest bits */
#include "lib/header_internal.h" /* XXX headerCheck */
#include "debug.h"
static const unsigned int nkeyids_max = 256;
static unsigned int nkeyids = 0;
static unsigned int nextkeyid = 0;
static unsigned int * keyids;
/** \ingroup header
* Translate and merge legacy signature tags into header.
* @param h header (dest)
* @param sigh signature header (src)
*/
static void headerMergeLegacySigs(Header h, Header sigh)
{
HeaderIterator hi;
struct rpmtd_s td;
hi = headerInitIterator(sigh);
for (; headerNext(hi, &td); rpmtdFreeData(&td))
{
switch (td.tag) {
/* XXX Translate legacy signature tag values. */
case RPMSIGTAG_SIZE:
td.tag = RPMTAG_SIGSIZE;
break;
case RPMSIGTAG_PGP:
td.tag = RPMTAG_SIGPGP;
break;
case RPMSIGTAG_MD5:
td.tag = RPMTAG_SIGMD5;
break;
case RPMSIGTAG_GPG:
td.tag = RPMTAG_SIGGPG;
break;
case RPMSIGTAG_PGP5:
td.tag = RPMTAG_SIGPGP5;
break;
case RPMSIGTAG_PAYLOADSIZE:
td.tag = RPMTAG_ARCHIVESIZE;
break;
case RPMSIGTAG_SHA1:
case RPMSIGTAG_DSA:
case RPMSIGTAG_RSA:
default:
if (!(td.tag >= HEADER_SIGBASE && td.tag < HEADER_TAGBASE))
continue;
break;
}
if (td.data == NULL) continue; /* XXX can't happen */
if (!headerIsEntry(h, td.tag)) {
if (hdrchkType(td.type))
continue;
if (td.count < 0 || hdrchkData(td.count))
continue;
switch(td.type) {
case RPM_NULL_TYPE:
continue;
break;
case RPM_CHAR_TYPE:
case RPM_INT8_TYPE:
case RPM_INT16_TYPE:
case RPM_INT32_TYPE:
case RPM_INT64_TYPE:
if (td.count != 1)
continue;
break;
case RPM_STRING_TYPE:
case RPM_BIN_TYPE:
if (td.count >= 16*1024)
continue;
break;
case RPM_STRING_ARRAY_TYPE:
case RPM_I18NSTRING_TYPE:
continue;
break;
}
(void) headerPut(h, &td, HEADERPUT_DEFAULT);
}
}
headerFreeIterator(hi);
}
/**
* Remember current key id.
* @param dig OpenPGP packet containter
* @return 0 if new keyid, otherwise 1
*/
static int stashKeyid(pgpDigParams sigp)
{
unsigned int keyid;
int i;
if (sigp == NULL)
return 0;
keyid = pgpGrab(sigp->signid+4, 4);
if (keyid == 0)
return 0;
if (keyids != NULL)
for (i = 0; i < nkeyids; i++) {
if (keyid == keyids[i])
return 1;
}
if (nkeyids < nkeyids_max) {
nkeyids++;
keyids = xrealloc(keyids, nkeyids * sizeof(*keyids));
}
if (keyids) /* XXX can't happen */
keyids[nextkeyid] = keyid;
nextkeyid++;
nextkeyid %= nkeyids_max;
return 0;
}
int parsePGPSig(rpmtd sigtd, const char *type, const char *fn,
pgpDigParams *sig)
{
int rc = pgpPrtParams(sigtd->data, sigtd->count, PGPTAG_SIGNATURE, sig);
if (rc != 0) {
if (type && fn) {
rpmlog(RPMLOG_ERR,
_("skipping %s %s with unverifiable signature\n"), type, fn);
} else if (type) {
rpmlog(RPMLOG_ERR,
_("skipping %s with unverifiable signature\n"), type);
}
}
return rc;
}
/*
* Argument monster to verify header-only signature/digest if there is
* one, otherwisereturn RPMRC_NOTFOUND to signal for plain sanity check.
*/
static rpmRC headerSigVerify(rpmKeyring keyring, rpmVSFlags vsflags,
int il, int dl, int ril, int rdl,
entryInfo pe, unsigned char * dataStart,
char **buf)
{
size_t siglen = 0;
rpmRC rc = RPMRC_FAIL;
pgpDigParams sig = NULL;
struct rpmtd_s sigtd;
struct entryInfo_s info, einfo;
unsigned int hashalgo = 0;
rpmtdReset(&sigtd);
memset(&info, 0, sizeof(info));
memset(&einfo, 0, sizeof(einfo));
/* Find a header-only digest/signature tag. */
for (int i = ril; i < il; i++) {
if (headerVerifyInfo(1, dl, pe+i, &einfo, 0) != -1) {
rasprintf(buf,
_("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
i, einfo.tag, einfo.type,
einfo.offset, einfo.count);
goto exit;
}
switch (einfo.tag) {
case RPMTAG_SHA1HEADER: {
size_t blen = 0;
unsigned const char * b;
if (vsflags & RPMVSF_NOSHA1HEADER)
break;
for (b = dataStart + einfo.offset; *b != '\0'; b++) {
if (strchr("0123456789abcdefABCDEF", *b) == NULL)
break;
blen++;
}
if (einfo.type != RPM_STRING_TYPE || *b != '\0' || blen != 40)
{
rasprintf(buf, _("hdr SHA1: BAD, not hex\n"));
goto exit;
}
if (info.tag == 0) {
info = einfo; /* structure assignment */
siglen = blen + 1;
}
} break;
case RPMTAG_RSAHEADER:
if (vsflags & RPMVSF_NORSAHEADER)
break;
if (einfo.type != RPM_BIN_TYPE) {
rasprintf(buf, _("hdr RSA: BAD, not binary\n"));
goto exit;
}
info = einfo; /* structure assignment */
siglen = info.count;
break;
case RPMTAG_DSAHEADER:
if (vsflags & RPMVSF_NODSAHEADER)
break;
if (einfo.type != RPM_BIN_TYPE) {
rasprintf(buf, _("hdr DSA: BAD, not binary\n"));
goto exit;
}
info = einfo; /* structure assignment */
siglen = info.count;
break;
default:
break;
}
}
/* No header-only digest/signature found, get outta here */
if (info.tag == 0) {
rc = RPMRC_NOTFOUND;
goto exit;
}
sigtd.tag = info.tag;
sigtd.type = info.type;
sigtd.count = info.count;
sigtd.data = memcpy(xmalloc(siglen), dataStart + info.offset, siglen);
sigtd.flags = RPMTD_ALLOCED;
switch (info.tag) {
case RPMTAG_RSAHEADER:
case RPMTAG_DSAHEADER:
if (parsePGPSig(&sigtd, "header", NULL, &sig))
goto exit;
hashalgo = pgpDigParamsAlgo(sig, PGPVAL_HASHALGO);
break;
case RPMTAG_SHA1HEADER:
hashalgo = PGPHASHALGO_SHA1;
break;
default:
break;
}
if (hashalgo) {
DIGEST_CTX ctx = rpmDigestInit(hashalgo, RPMDIGEST_NONE);
int32_t ildl[2] = { htonl(ril), htonl(rdl) };
rpmDigestUpdate(ctx, rpm_header_magic, sizeof(rpm_header_magic));
rpmDigestUpdate(ctx, ildl, sizeof(ildl));
rpmDigestUpdate(ctx, pe, (ril * sizeof(*pe)));
rpmDigestUpdate(ctx, dataStart, rdl);
rc = rpmVerifySignature(keyring, &sigtd, sig, ctx, buf);
rpmDigestFinal(ctx, NULL, NULL, 0);
}
exit:
rpmtdFreeData(&sigtd);
pgpDigParamsFree(sig);
return rc;
}
static rpmRC headerVerify(rpmKeyring keyring, rpmVSFlags vsflags,
const void * uh, size_t uc, char ** msg)
{
char *buf = NULL;
int32_t * ei = (int32_t *) uh;
int32_t il = ntohl(ei[0]);
int32_t dl = ntohl(ei[1]);
entryInfo pe = (entryInfo) &ei[2];
int32_t pvlen = sizeof(il) + sizeof(dl) + (il * sizeof(*pe)) + dl;
unsigned char * dataStart = (unsigned char *) (pe + il);
struct indexEntry_s entry;
struct entryInfo_s info;
int32_t ril = 0;
unsigned char * regionEnd = NULL;
rpmRC rc = RPMRC_FAIL; /* assume failure */
/* Is the blob the right size? */
if (uc > 0 && pvlen != uc) {
rasprintf(&buf, _("blob size(%d): BAD, 8 + 16 * il(%d) + dl(%d)\n"),
(int)uc, (int)il, (int)dl);
goto exit;
}
memset(&entry, 0, sizeof(entry));
memset(&info, 0, sizeof(info));
/* Check (and convert) the 1st tag element. */
if (headerVerifyInfo(1, dl, pe, &entry.info, 0) != -1) {
rasprintf(&buf, _("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
0, entry.info.tag, entry.info.type,
entry.info.offset, entry.info.count);
goto exit;
}
/* Is there an immutable header region tag? */
if (!(entry.info.tag == RPMTAG_HEADERIMMUTABLE)) {
rc = RPMRC_NOTFOUND;
goto exit;
}
/* Is the region tag sane? */
if (!(entry.info.type == REGION_TAG_TYPE &&
entry.info.count == REGION_TAG_COUNT)) {
rasprintf(&buf,
_("region tag: BAD, tag %d type %d offset %d count %d\n"),
entry.info.tag, entry.info.type,
entry.info.offset, entry.info.count);
goto exit;
}
/* Is the trailer within the data area? */
if (entry.info.offset + REGION_TAG_COUNT > dl) {
rasprintf(&buf,
_("region offset: BAD, tag %d type %d offset %d count %d\n"),
entry.info.tag, entry.info.type,
entry.info.offset, entry.info.count);
goto exit;
}
/* Is there an immutable header region tag trailer? */
regionEnd = dataStart + entry.info.offset;
(void) memcpy(&info, regionEnd, REGION_TAG_COUNT);
regionEnd += REGION_TAG_COUNT;
if (headerVerifyInfo(1, il * sizeof(*pe), &info, &entry.info, 1) != -1 ||
!(entry.info.tag == RPMTAG_HEADERIMMUTABLE
&& entry.info.type == REGION_TAG_TYPE
&& entry.info.count == REGION_TAG_COUNT))
{
rasprintf(&buf,
_("region trailer: BAD, tag %d type %d offset %d count %d\n"),
entry.info.tag, entry.info.type,
entry.info.offset, entry.info.count);
goto exit;
}
memset(&info, 0, sizeof(info));
/* Is the no. of tags in the region less than the total no. of tags? */
ril = entry.info.offset/sizeof(*pe);
if ((entry.info.offset % sizeof(*pe)) || ril > il) {
rasprintf(&buf, _("region size: BAD, ril(%d) > il(%d)\n"), ril, il);
goto exit;
}
/* Verify header-only digest/signature if there is one we can use. */
rc = headerSigVerify(keyring, vsflags,
il, dl, ril, (regionEnd - dataStart),
pe, dataStart, &buf);
exit:
/* If no header-only digest/signature, then do simple sanity check. */
if (rc == RPMRC_NOTFOUND) {
int xx = headerVerifyInfo(ril-1, dl, pe+1, &entry.info, 0);
if (xx != -1) {
rasprintf(&buf,
_("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
xx+1, entry.info.tag, entry.info.type,
entry.info.offset, entry.info.count);
rc = RPMRC_FAIL;
} else {
rasprintf(&buf, "Header sanity check: OK\n");
rc = RPMRC_OK;
}
}
if (msg)
*msg = buf;
else
free(buf);
return rc;
}
rpmRC headerCheck(rpmts ts, const void * uh, size_t uc, char ** msg)
{
rpmRC rc;
rpmVSFlags vsflags = rpmtsVSFlags(ts);
rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
rpmswEnter(rpmtsOp(ts, RPMTS_OP_DIGEST), 0);
rc = headerVerify(keyring, vsflags, uh, uc, msg);
rpmswExit(rpmtsOp(ts, RPMTS_OP_DIGEST), uc);
rpmKeyringFree(keyring);
return rc;
}
static rpmRC rpmpkgReadHeader(rpmKeyring keyring, rpmVSFlags vsflags,
FD_t fd, Header *hdrp, char ** msg)
{
char *buf = NULL;
int32_t block[4];
int32_t il;
int32_t dl;
int32_t * ei = NULL;
size_t uc;
size_t nb;
Header h = NULL;
rpmRC rc = RPMRC_FAIL; /* assume failure */
int xx;
if (hdrp)
*hdrp = NULL;
if (msg)
*msg = NULL;
memset(block, 0, sizeof(block));
if ((xx = Fread(block, 1, sizeof(block), fd)) != sizeof(block)) {
rasprintf(&buf,
_("hdr size(%d): BAD, read returned %d\n"), (int)sizeof(block), xx);
goto exit;
}
if (memcmp(block, rpm_header_magic, sizeof(rpm_header_magic))) {
rasprintf(&buf, _("hdr magic: BAD\n"));
goto exit;
}
il = ntohl(block[2]);
if (hdrchkTags(il)) {
rasprintf(&buf, _("hdr tags: BAD, no. of tags(%d) out of range\n"), il);
goto exit;
}
dl = ntohl(block[3]);
if (hdrchkData(dl)) {
rasprintf(&buf,
_("hdr data: BAD, no. of bytes(%d) out of range\n"), dl);
goto exit;
}
nb = (il * sizeof(struct entryInfo_s)) + dl;
uc = sizeof(il) + sizeof(dl) + nb;
ei = xmalloc(uc);
ei[0] = block[2];
ei[1] = block[3];
if ((xx = Fread((char *)&ei[2], 1, nb, fd)) != nb) {
rasprintf(&buf, _("hdr blob(%zd): BAD, read returned %d\n"), nb, xx);
goto exit;
}
/* Sanity check header tags */
rc = headerVerify(keyring, vsflags, ei, uc, &buf);
if (rc != RPMRC_OK)
goto exit;
/* OK, blob looks sane, load the header. */
h = headerImport(ei, uc, 0);
if (h == NULL) {
free(buf);
rasprintf(&buf, _("hdr load: BAD\n"));
rc = RPMRC_FAIL;
goto exit;
}
ei = NULL; /* XXX will be freed with header */
exit:
if (hdrp && h && rc == RPMRC_OK)
*hdrp = headerLink(h);
free(ei);
headerFree(h);
if (msg != NULL && *msg == NULL && buf != NULL) {
*msg = buf;
} else {
free(buf);
}
return rc;
}
rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg)
{
rpmRC rc;
rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
rpmVSFlags vsflags = rpmtsVSFlags(ts);
rc = rpmpkgReadHeader(keyring, vsflags, fd, hdrp, msg);
rpmKeyringFree(keyring);
return rc;
}
static rpmRC rpmpkgRead(rpmKeyring keyring, rpmVSFlags vsflags,
FD_t fd, const char * fn, Header * hdrp)
{
pgpDigParams sig = NULL;
char buf[8*BUFSIZ];
ssize_t count;
Header sigh = NULL;
rpmTagVal sigtag;
struct rpmtd_s sigtd;
Header h = NULL;
char * msg = NULL;
rpmRC rc = RPMRC_FAIL; /* assume failure */
int leadtype = -1;
headerGetFlags hgeflags = HEADERGET_DEFAULT;
DIGEST_CTX ctx = NULL;
if (hdrp) *hdrp = NULL;
if (fn == NULL)
fn = Fdescr(fd);
rpmtdReset(&sigtd);
if ((rc = rpmLeadRead(fd, NULL, &leadtype, &msg)) != RPMRC_OK) {
/* Avoid message spew on manifests */
if (rc != RPMRC_NOTFOUND)
rpmlog(RPMLOG_ERR, "%s: %s\n", fn, msg);
free(msg);
goto exit;
}
/* Read the signature header. */
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);
rc = RPMRC_FAIL;
goto exit;
}
break;
}
msg = _free(msg);
#define _chk(_mask, _tag) \
(sigtag == 0 && !(vsflags & (_mask)) && headerIsEntry(sigh, (_tag)))
/*
* Figger the most effective available signature.
* Prefer signatures over digests, then header-only over header+payload.
* DSA will be preferred over RSA if both exist because tested first.
* Note that NEEDPAYLOAD prevents header+payload signatures and digests.
*/
sigtag = 0;
if (_chk(RPMVSF_NODSAHEADER, RPMSIGTAG_DSA)) {
sigtag = RPMSIGTAG_DSA;
} else if (_chk(RPMVSF_NORSAHEADER, RPMSIGTAG_RSA)) {
sigtag = RPMSIGTAG_RSA;
} else if (_chk(RPMVSF_NODSA|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_GPG)) {
sigtag = RPMSIGTAG_GPG;
fdInitDigest(fd, PGPHASHALGO_SHA1, 0);
} else if (_chk(RPMVSF_NORSA|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_PGP)) {
sigtag = RPMSIGTAG_PGP;
fdInitDigest(fd, PGPHASHALGO_MD5, 0);
} else if (_chk(RPMVSF_NOSHA1HEADER, RPMSIGTAG_SHA1)) {
sigtag = RPMSIGTAG_SHA1;
} else if (_chk(RPMVSF_NOMD5|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_MD5)) {
sigtag = RPMSIGTAG_MD5;
fdInitDigest(fd, PGPHASHALGO_MD5, 0);
}
/* Read the metadata, computing digest(s) on the fly. */
h = NULL;
msg = NULL;
rc = rpmpkgReadHeader(keyring, vsflags, fd, &h, &msg);
if (rc != RPMRC_OK || h == NULL) {
rpmlog(RPMLOG_ERR, _("%s: headerRead failed: %s"), fn,
(msg && *msg ? msg : "\n"));
msg = _free(msg);
goto exit;
}
msg = _free(msg);
/* Any digests or signatures to check? */
if (sigtag == 0) {
rc = RPMRC_OK;
goto exit;
}
/* Retrieve the tag parameters from the signature header. */
if (!headerGet(sigh, sigtag, &sigtd, hgeflags)) {
rc = RPMRC_FAIL;
goto exit;
}
switch (sigtag) {
case RPMSIGTAG_RSA:
case RPMSIGTAG_DSA:
if (parsePGPSig(&sigtd, "package", fn, &sig))
goto exit;
/* fallthrough */
case RPMSIGTAG_SHA1:
{ struct rpmtd_s utd;
unsigned int hashalgo = (sigtag == RPMSIGTAG_SHA1) ?
PGPHASHALGO_SHA1 :
pgpDigParamsAlgo(sig, PGPVAL_HASHALGO);
if (!headerGet(h, RPMTAG_HEADERIMMUTABLE, &utd, hgeflags))
break;
ctx = rpmDigestInit(hashalgo, RPMDIGEST_NONE);
(void) rpmDigestUpdate(ctx, rpm_header_magic, sizeof(rpm_header_magic));
(void) rpmDigestUpdate(ctx, utd.data, utd.count);
rpmtdFreeData(&utd);
} break;
case RPMSIGTAG_GPG:
case RPMSIGTAG_PGP5: /* XXX legacy */
case RPMSIGTAG_PGP:
if (parsePGPSig(&sigtd, "package", fn, &sig))
goto exit;
/* fallthrough */
case RPMSIGTAG_MD5:
/* Legacy signatures need the compressed payload in the digest too. */
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));
rc = RPMRC_FAIL;
goto exit;
}
ctx = rpmDigestBundleDupCtx(fdGetBundle(fd),(sigtag == RPMSIGTAG_MD5) ?
PGPHASHALGO_MD5 :
pgpDigParamsAlgo(sig, PGPVAL_HASHALGO));
break;
default:
break;
}
/** @todo Implement disable/enable/warn/error/anal policy. */
rc = rpmVerifySignature(keyring, &sigtd, sig, ctx, &msg);
switch (rc) {
case RPMRC_OK: /* Signature is OK. */
rpmlog(RPMLOG_DEBUG, "%s: %s", fn, msg);
break;
case RPMRC_NOTTRUSTED: /* Signature is OK, but key is not trusted. */
case RPMRC_NOKEY: /* Public key is unavailable. */
/* XXX Print NOKEY/NOTTRUSTED warning only once. */
{ int lvl = (stashKeyid(sig) ? RPMLOG_DEBUG : RPMLOG_WARNING);
rpmlog(lvl, "%s: %s", fn, msg);
} break;
case RPMRC_NOTFOUND: /* Signature is unknown type. */
rpmlog(RPMLOG_WARNING, "%s: %s", fn, msg);
break;
default:
case RPMRC_FAIL: /* Signature does not verify. */
rpmlog(RPMLOG_ERR, "%s: %s", fn, msg);
break;
}
free(msg);
exit:
if (rc != RPMRC_FAIL && h != NULL && hdrp != NULL) {
/* Retrofit RPMTAG_SOURCEPACKAGE to srpms for compatibility */
if (leadtype == RPMLEAD_SOURCE && headerIsSource(h)) {
if (!headerIsEntry(h, RPMTAG_SOURCEPACKAGE)) {
uint32_t one = 1;
headerPutUint32(h, RPMTAG_SOURCEPACKAGE, &one, 1);
}
}
/*
* Try to make sure binary rpms have RPMTAG_SOURCERPM set as that's
* what we use for differentiating binary vs source elsewhere.
*/
if (!headerIsEntry(h, RPMTAG_SOURCEPACKAGE) && headerIsSource(h)) {
headerPutString(h, RPMTAG_SOURCERPM, "(none)");
}
/*
* Convert legacy headers on the fly. Not having "new" style compressed
* filenames is close enough estimate for legacy indication...
*/
if (!headerIsEntry(h, RPMTAG_DIRNAMES)) {
headerConvert(h, HEADERCONV_RETROFIT_V3);
}
/* Append (and remap) signature tags to the metadata. */
headerMergeLegacySigs(h, sigh);
/* Bump reference count for return. */
*hdrp = headerLink(h);
}
rpmtdFreeData(&sigtd);
rpmDigestFinal(ctx, NULL, NULL, 0);
h = headerFree(h);
pgpDigParamsFree(sig);
sigh = rpmFreeSignature(sigh);
return rc;
}
rpmRC rpmReadPackageFile(rpmts ts, FD_t fd, const char * fn, Header * hdrp)
{
rpmRC rc;
rpmVSFlags vsflags = rpmtsVSFlags(ts);
rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
rc = rpmpkgRead(keyring, vsflags, fd, fn, hdrp);
rpmKeyringFree(keyring);
return rc;
}