rpm/lib/rpmvs.c

594 lines
15 KiB
C
Raw Normal View History

#include "system.h"
#include <pthread.h>
#include <rpm/rpmkeyring.h>
#include <rpm/rpmmacro.h>
#include <rpm/rpmlog.h>
#include "lib/rpmvs.h"
#include "rpmio/digest.h"
#include "debug.h"
struct rpmvs_s {
struct rpmsinfo_s *sigs;
int nsigs;
int nalloced;
rpmVSFlags vsflags;
rpmDigestBundle bundle;
rpmKeyring keyring;
int vfylevel;
};
struct vfytag_s {
rpmTagVal tag;
rpmTagType tagtype;
rpm_count_t tagcount;
rpm_count_t tagsize;
};
static const struct vfytag_s rpmvfytags[] = {
{ RPMSIGTAG_SIZE, RPM_BIN_TYPE, 0, 0, },
{ RPMSIGTAG_PGP, RPM_BIN_TYPE, 0, 0, },
{ RPMSIGTAG_MD5, RPM_BIN_TYPE, 0, 16, },
{ RPMSIGTAG_GPG, RPM_BIN_TYPE, 0, 0, },
{ RPMSIGTAG_PAYLOADSIZE, RPM_INT32_TYPE, 1, 4, },
{ RPMSIGTAG_RESERVEDSPACE, RPM_BIN_TYPE, 0, 0, },
{ RPMTAG_DSAHEADER, RPM_BIN_TYPE, 0, 0, },
{ RPMTAG_RSAHEADER, RPM_BIN_TYPE, 0, 0, },
{ RPMTAG_SHA1HEADER, RPM_STRING_TYPE, 1, 41, },
{ RPMSIGTAG_LONGSIZE, RPM_INT64_TYPE, 1, 8, },
{ RPMSIGTAG_LONGARCHIVESIZE, RPM_INT64_TYPE, 1, 8, },
{ RPMTAG_SHA256HEADER, RPM_STRING_TYPE, 1, 65, },
{ RPMTAG_PAYLOADDIGEST, RPM_STRING_ARRAY_TYPE, 0, 0, },
{ RPMTAG_PAYLOADDIGESTALT, RPM_STRING_ARRAY_TYPE, 0, 0, },
{ 0 } /* sentinel */
};
struct vfyinfo_s {
rpmTagVal tag;
int sigh;
struct rpmsinfo_s vi;
};
static const struct vfyinfo_s rpmvfyitems[] = {
{ RPMSIGTAG_SIZE, 1,
{ RPMSIG_OTHER_TYPE, 0,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), 0, 0, }, },
{ RPMSIGTAG_PGP, 1,
{ RPMSIG_SIGNATURE_TYPE, RPMVSF_NORSA,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), 0, PGPPUBKEYALGO_RSA, }, },
{ RPMSIGTAG_MD5, 1,
{ RPMSIG_DIGEST_TYPE, RPMVSF_NOMD5,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), RPM_HASH_MD5, }, },
{ RPMSIGTAG_GPG, 1,
{ RPMSIG_SIGNATURE_TYPE, RPMVSF_NODSA,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), 0, PGPPUBKEYALGO_DSA, }, },
{ RPMSIGTAG_PAYLOADSIZE, 1,
{ RPMSIG_OTHER_TYPE, 0,
(RPMSIG_PAYLOAD), 0, 0, }, },
{ RPMSIGTAG_RESERVEDSPACE, 1,
{ RPMSIG_OTHER_TYPE, 0,
0, 0, 0, }, },
{ RPMTAG_DSAHEADER, 1,
{ RPMSIG_SIGNATURE_TYPE, RPMVSF_NODSAHEADER,
(RPMSIG_HEADER), 0, PGPPUBKEYALGO_DSA, }, },
{ RPMTAG_RSAHEADER, 1,
{ RPMSIG_SIGNATURE_TYPE, RPMVSF_NORSAHEADER,
(RPMSIG_HEADER), 0, PGPPUBKEYALGO_RSA, }, },
{ RPMTAG_SHA1HEADER, 1,
{ RPMSIG_DIGEST_TYPE, RPMVSF_NOSHA1HEADER,
(RPMSIG_HEADER), RPM_HASH_SHA1, 0, }, },
{ RPMSIGTAG_LONGSIZE, 1,
{ RPMSIG_OTHER_TYPE, 0,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), 0, 0, }, },
{ RPMSIGTAG_LONGARCHIVESIZE, 1,
{ RPMSIG_OTHER_TYPE, 0,
(RPMSIG_HEADER|RPMSIG_PAYLOAD), 0, 0, }, },
{ RPMTAG_SHA256HEADER, 1,
{ RPMSIG_DIGEST_TYPE, RPMVSF_NOSHA256HEADER,
(RPMSIG_HEADER), RPM_HASH_SHA256, 0, }, },
{ RPMTAG_PAYLOADDIGEST, 0,
{ RPMSIG_DIGEST_TYPE, RPMVSF_NOPAYLOAD,
(RPMSIG_PAYLOAD), RPM_HASH_SHA256, 0, }, },
{ RPMTAG_PAYLOADDIGESTALT, 0,
{ RPMSIG_DIGEST_TYPE, RPMVSF_NOPAYLOAD,
(RPMSIG_PAYLOAD), RPM_HASH_SHA256, 0, 1, }, },
{ 0 } /* sentinel */
};
static const char *rangeName(int range);
static const char * rpmSigString(rpmRC res);
static void rpmVerifySignature(rpmKeyring keyring, struct rpmsinfo_s *sinfo);
static int sinfoLookup(rpmTagVal tag)
{
const struct vfyinfo_s *start = &rpmvfyitems[0];
int ix = -1;
for (const struct vfyinfo_s *si = start; si->tag; si++) {
if (tag == si->tag) {
ix = si - start;
break;
}
}
return ix;
}
static int validHex(const char *str, size_t slen)
{
int valid = 0; /* Assume invalid */
const char *b;
/* Our hex data is always even sized and at least sha-1 long */
if (slen % 2 || slen < 40)
goto exit;
for (b = str ; *b != '\0'; b++) {
if (strchr("0123456789abcdefABCDEF", *b) == NULL)
goto exit;
}
valid = 1;
exit:
return valid;
}
static void rpmsinfoInit(const struct vfyinfo_s *vinfo,
const struct vfytag_s *tinfo,
rpmtd td, const char *origin,
struct rpmsinfo_s *sinfo)
{
rpmRC rc = RPMRC_FAIL;
const void *data = NULL;
rpm_count_t dlen = 0;
*sinfo = vinfo->vi; /* struct assignment */
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
sinfo->wrapped = (vinfo->sigh == 0);
sinfo->strength = sinfo->type;
if (td == NULL) {
rc = RPMRC_NOTFOUND;
goto exit;
}
if (tinfo->tagtype && tinfo->tagtype != td->type) {
rasprintf(&sinfo->msg, _("%s tag %u: invalid type %u"),
origin, td->tag, td->type);
goto exit;
}
if (tinfo->tagcount && tinfo->tagcount != td->count) {
rasprintf(&sinfo->msg, _("%s: tag %u: invalid count %u"),
origin, td->tag, td->count);
goto exit;
}
switch (td->type) {
case RPM_STRING_TYPE:
case RPM_STRING_ARRAY_TYPE:
data = rpmtdGetString(td);
if (data)
dlen = strlen(data);
break;
case RPM_BIN_TYPE:
data = td->data;
dlen = td->count;
break;
}
/* MD5 has data length of 16, everything else is (much) larger */
if (sinfo->hashalgo && (data == NULL || dlen < 16)) {
rasprintf(&sinfo->msg, _("%s tag %u: invalid data %p (%u)"),
origin, td->tag, data, dlen);
goto exit;
}
if (td->type == RPM_STRING_TYPE && td->size == 0)
td->size = dlen + 1;
if (tinfo->tagsize && (td->flags & RPMTD_IMMUTABLE) &&
tinfo->tagsize != td->size) {
rasprintf(&sinfo->msg, _("%s tag %u: invalid size %u"),
origin, td->tag, td->size);
goto exit;
}
if (sinfo->type == RPMSIG_SIGNATURE_TYPE) {
if (pgpPrtParams(data, dlen, PGPTAG_SIGNATURE, &sinfo->sig)) {
rasprintf(&sinfo->msg, _("%s tag %u: invalid OpenPGP signature"),
origin, td->tag);
goto exit;
}
sinfo->hashalgo = pgpDigParamsAlgo(sinfo->sig, PGPVAL_HASHALGO);
sinfo->keyid = pgpGrab(sinfo->sig->signid+4, 4);
} else if (sinfo->type == RPMSIG_DIGEST_TYPE) {
if (td->type == RPM_BIN_TYPE) {
sinfo->dig = rpmhex(data, dlen);
} else {
if (!validHex(data, dlen)) {
rasprintf(&sinfo->msg,
_("%s: tag %u: invalid hex"), origin, td->tag);
goto exit;
}
sinfo->dig = xstrdup(data);
}
}
if (sinfo->hashalgo)
sinfo->id = (td->tag << 16) | rpmtdGetIndex(td);
rc = RPMRC_OK;
exit:
sinfo->rc = rc;
return;
}
static void rpmsinfoFini(struct rpmsinfo_s *sinfo)
{
if (sinfo) {
if (sinfo->type == RPMSIG_SIGNATURE_TYPE)
pgpDigParamsFree(sinfo->sig);
else if (sinfo->type == RPMSIG_DIGEST_TYPE)
free(sinfo->dig);
rpmDigestFinal(sinfo->ctx, NULL, NULL, 0);
free(sinfo->msg);
free(sinfo->descr);
memset(sinfo, 0, sizeof(*sinfo));
}
}
static int rpmsinfoDisabled(const struct rpmsinfo_s *sinfo, rpmVSFlags vsflags)
{
if (!(sinfo->type & RPMSIG_VERIFIABLE_TYPE))
return 1;
if (vsflags & sinfo->disabler)
return 1;
if ((vsflags & RPMVSF_NEEDPAYLOAD) && (sinfo->range & RPMSIG_PAYLOAD))
return 1;
return 0;
}
static void rpmvsReserve(struct rpmvs_s *vs, int n)
{
if (vs->nsigs + n >= vs->nalloced) {
vs->nalloced = (vs->nsigs * 2) + n;
vs->sigs = xrealloc(vs->sigs, vs->nalloced * sizeof(*vs->sigs));
}
}
const char *rpmsinfoDescr(struct rpmsinfo_s *sinfo)
{
if (sinfo->descr == NULL) {
switch (sinfo->type) {
case RPMSIG_DIGEST_TYPE:
rasprintf(&sinfo->descr, _("%s%s%s %s"),
rangeName(sinfo->range),
pgpValString(PGPVAL_HASHALGO, sinfo->hashalgo),
sinfo->alt ? " ALT" : "",
_("digest"));
break;
case RPMSIG_SIGNATURE_TYPE:
if (sinfo->sig) {
char *t = pgpIdentItem(sinfo->sig);
rasprintf(&sinfo->descr, _("%s%s"),
rangeName(sinfo->range), t);
free(t);
} else {
rasprintf(&sinfo->descr, _("%s%s%s %s"),
rangeName(sinfo->range),
pgpValString(PGPVAL_PUBKEYALGO, sinfo->sigalgo),
sinfo->alt ? " ALT" : "",
_("signature"));
}
break;
}
}
return sinfo->descr;
}
char *rpmsinfoMsg(struct rpmsinfo_s *sinfo)
{
char *msg = NULL;
if (sinfo->msg) {
rasprintf(&msg, "%s: %s (%s)",
rpmsinfoDescr(sinfo), rpmSigString(sinfo->rc), sinfo->msg);
} else {
rasprintf(&msg, "%s: %s",
rpmsinfoDescr(sinfo), rpmSigString(sinfo->rc));
}
return msg;
}
static void rpmvsAppend(struct rpmvs_s *sis, hdrblob blob,
const struct vfyinfo_s *vi, const struct vfytag_s *ti)
{
if (!(vi->vi.type & RPMSIG_VERIFIABLE_TYPE))
return;
const char *o = (blob->il > blob->ril) ? _("header") : _("package");
struct rpmtd_s td;
rpmRC rc = hdrblobGet(blob, vi->tag, &td);
int nitems = (rc == RPMRC_OK) ? rpmtdCount(&td) : 1;
rpmvsReserve(sis, nitems);
if (!rpmsinfoDisabled(&vi->vi, sis->vsflags) && rc == RPMRC_OK) {
while (rpmtdNext(&td) >= 0) {
rpmsinfoInit(vi, ti, &td, o, &sis->sigs[sis->nsigs]);
sis->nsigs++;
}
} else {
rpmsinfoInit(vi, ti, NULL, o, &sis->sigs[sis->nsigs]);
sis->nsigs++;
}
rpmtdFreeData(&td);
}
void rpmvsAppendTag(struct rpmvs_s *vs, hdrblob blob, rpmTagVal tag)
{
int ix = sinfoLookup(tag);
if (ix >= 0) {
const struct vfyinfo_s *vi = &rpmvfyitems[ix];
const struct vfytag_s *ti = &rpmvfytags[ix];
rpmvsAppend(vs, blob, vi, ti);
}
}
struct rpmvs_s *rpmvsCreate(int vfylevel, rpmVSFlags vsflags, rpmKeyring keyring)
{
struct rpmvs_s *sis = xcalloc(1, sizeof(*sis));
sis->vsflags = vsflags;
sis->keyring = rpmKeyringLink(keyring);
sis->vfylevel = vfylevel;
return sis;
}
rpmVSFlags rpmvsFlags(struct rpmvs_s *vs)
{
return vs->vsflags;
}
void rpmvsInit(struct rpmvs_s *vs, hdrblob blob, rpmDigestBundle bundle)
{
const struct vfyinfo_s *si = &rpmvfyitems[0];
const struct vfytag_s *ti = &rpmvfytags[0];
for (; si->tag && ti->tag; si++, ti++) {
/* Ignore non-signature tags initially */
if (!si->sigh)
continue;
rpmvsAppend(vs, blob, si, ti);
}
vs->bundle = bundle;
}
struct rpmvs_s *rpmvsFree(struct rpmvs_s *sis)
{
if (sis) {
rpmKeyringFree(sis->keyring);
for (int i = 0; i < sis->nsigs; i++) {
rpmsinfoFini(&sis->sigs[i]);
}
free(sis->sigs);
free(sis);
}
return NULL;
}
void rpmvsInitRange(struct rpmvs_s *sis, int range)
{
for (int i = 0; i < sis->nsigs; i++) {
struct rpmsinfo_s *sinfo = &sis->sigs[i];
if (sinfo->range & range) {
if (sinfo->rc == RPMRC_OK)
rpmDigestBundleAddID(sis->bundle, sinfo->hashalgo, sinfo->id, 0);
}
}
}
void rpmvsFiniRange(struct rpmvs_s *sis, int range)
{
for (int i = 0; i < sis->nsigs; i++) {
struct rpmsinfo_s *sinfo = &sis->sigs[i];
if (sinfo->range == range && sinfo->rc == RPMRC_OK) {
sinfo->ctx = rpmDigestBundleDupCtx(sis->bundle, sinfo->id);
/* Handle unsupported digests the same as disabled ones */
if (sinfo->ctx == NULL)
sinfo->rc = RPMRC_NOTFOUND;
rpmDigestBundleFinal(sis->bundle, sinfo->id, NULL, NULL, 0);
}
}
}
int rpmvsRange(struct rpmvs_s *vs)
{
int range = 0;
for (int i = 0; i < vs->nsigs; i++) {
if (rpmsinfoDisabled(&vs->sigs[i], vs->vsflags))
continue;
range |= vs->sigs[i].range;
}
return range;
}
static int sinfoCmp(const void *a, const void *b)
{
const struct rpmsinfo_s *sa = a;
const struct rpmsinfo_s *sb = b;
int rc = sa->range - sb->range;
/* signatures before digests */
if (rc == 0)
rc = sb->type - sa->type;
/* strongest (in the "newer is better" sense) algos first */
if (rc == 0)
rc = sb->sigalgo - sb->sigalgo;
if (rc == 0)
rc = sb->hashalgo - sb->hashalgo;
/* last resort, these only makes sense from consistency POV */
if (rc == 0)
rc = sb->id - sa->id;
if (rc == 0)
rc = sb->disabler - sa->disabler;
return rc;
}
static const struct rpmsinfo_s *getAlt(const struct rpmvs_s *vs, const struct rpmsinfo_s *sinfo)
{
for (int i = 0; i < vs->nsigs; i++) {
const struct rpmsinfo_s *alt = &vs->sigs[i];
if ((sinfo->id != alt->id) && (sinfo->disabler == alt->disabler))
return alt;
}
return NULL;
}
int rpmvsVerify(struct rpmvs_s *sis, int type,
rpmsinfoCb cb, void *cbdata)
{
int failed = 0;
int cont = 1;
int range = 0, vfylevel = sis->vfylevel;
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
int verified[3] = { 0, 0, 0 };
/* sort for consistency and rough "better comes first" semantics*/
qsort(sis->sigs, sis->nsigs, sizeof(*sis->sigs), sinfoCmp);
for (int i = 0; i < sis->nsigs && cont; i++) {
struct rpmsinfo_s *sinfo = &sis->sigs[i];
if (type & sinfo->type) {
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
/* Digests in signed header are signature strength */
if (sinfo->wrapped) {
if (verified[RPMSIG_SIGNATURE_TYPE] & RPMSIG_HEADER)
sinfo->strength = RPMSIG_SIGNATURE_TYPE;
}
if (sinfo->ctx) {
rpmVerifySignature(sis->keyring, sinfo);
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
if (sinfo->rc == RPMRC_OK) {
verified[sinfo->type] |= sinfo->range;
verified[sinfo->strength] |= sinfo->range;
}
}
range |= sinfo->range;
}
}
/* Unconditionally reject partially signed packages */
if (verified[RPMSIG_SIGNATURE_TYPE])
vfylevel |= RPMSIG_SIGNATURE_TYPE;
/* Cannot verify payload if RPMVSF_NEEDPAYLOAD is set */
if (sis->vsflags & RPMVSF_NEEDPAYLOAD)
range &= ~RPMSIG_PAYLOAD;
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
for (int i = 0; i < sis->nsigs && cont; i++) {
struct rpmsinfo_s *sinfo = &sis->sigs[i];
int strength = (sinfo->type | sinfo->strength);
int required = 0;
/* Ignore failure if an alternative exists and verifies ok */
if (sinfo->rc == RPMRC_FAIL) {
const struct rpmsinfo_s * alt = getAlt(sis, sinfo);
if (alt && alt->rc == RPMRC_OK)
sinfo->rc = RPMRC_NOTFOUND;
}
if (vfylevel & strength & RPMSIG_DIGEST_TYPE) {
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
int missing = (range & ~verified[RPMSIG_DIGEST_TYPE]);
required |= (missing & sinfo->range);
}
if (vfylevel & strength & RPMSIG_SIGNATURE_TYPE) {
Implement configurable, mandatory signature/digest verify level for rpm -K Traditionally rpm has verified whatever digests and signatures are present (unless disabled) but there hasn't been any way to *require* either type of verification, and making it all too easy to fool rpm into accepting a tampered package whose signatures and digests have just been stripped off and content modified. This commit adds support for required verification level, possible levels being "none" (for traditional behavior and default for now), "digest", "signature" and "all" (for both signatures and digests). What it means is that the specified level of validation is required for the entire package file: eg on digest level, both the header and the payload must have been validated with a digest. All digest algorithms are considered equal for this purpose to abstract out version differences: eg all valid rpm packages have *some* digest covering the entire package. Traditionally the only digest covering the payload is the md5sum, but packages built with rpm >= 4.14.0 have a separate payload digest which can be used for the purpose and md5sum disabled (which would also deny install of older packages). Similarly (and more importantly) signatures can now be also required. As a special case, the new payload digest added in 4.14.x is in the main header which, if signed, makes the payload digest signature strength too. Which would make it possible to drop V3 header+payload signatures from package signing (which would make signing much faster) and still have signature strength coverage for the entire package. It's worth noting that when a verification level is active, --nodigest and --nosignature seem to behave sort of counter-intuitively: these disablers tell rpm not to try verification of those items, but the verification level specifies what *must* be verified for a pass, so eg if signature verify level is active, rpm -K --nosignatures will *always fail* because no signatures could be verified. This only implements the "policy" for the rpm -K style signature checking path, installation is not covered yet as that's quite a different beast: we don't want to enforce this at rpmReadPackage() level because of API issues and assumptions (eg you couldn't read a package to discover what key it was signed with, in order to import the key required to successfully verify said package)
2018-04-27 18:27:02 +08:00
int missing = (range & ~verified[RPMSIG_SIGNATURE_TYPE]);
required |= (missing & sinfo->range);
}
if (!required && sinfo->rc == RPMRC_NOTFOUND)
continue;
if (cb)
cont = cb(sinfo, cbdata);
if (sinfo->rc != RPMRC_OK)
failed = 1;
}
return failed;
}
static const char * rpmSigString(rpmRC res)
{
const char * str;
switch (res) {
case RPMRC_OK: str = "OK"; break;
case RPMRC_FAIL: str = "BAD"; break;
case RPMRC_NOKEY: str = "NOKEY"; break;
case RPMRC_NOTTRUSTED: str = "NOTTRUSTED"; break;
case RPMRC_NOTFOUND: str = "NOTFOUND"; break;
default: str = "UNKNOWN"; break;
}
return str;
}
static const char *rangeName(int range)
{
switch (range) {
case RPMSIG_HEADER: return _("Header ");
case RPMSIG_PAYLOAD: return _("Payload ");
}
/* trad. output for (RPMSIG_HEADER|RPMSIG_PAYLOAD) range is "" */
return "";
}
static rpmRC verifyDigest(struct rpmsinfo_s *sinfo)
{
rpmRC res = RPMRC_FAIL; /* assume failure */
char * dig = NULL;
size_t diglen = 0;
DIGEST_CTX ctx = rpmDigestDup(sinfo->ctx);
if (rpmDigestFinal(ctx, (void **)&dig, &diglen, 1) || diglen == 0)
goto exit;
if (strcasecmp(sinfo->dig, dig) == 0) {
res = RPMRC_OK;
} else {
rasprintf(&sinfo->msg, "Expected %s != %s", sinfo->dig, dig);
}
exit:
free(dig);
return res;
}
/**
* Verify DSA/RSA signature.
* @param keyring pubkey keyring
* @param sinfo OpenPGP signature parameters
* @return RPMRC_OK on success
*/
static rpmRC
verifySignature(rpmKeyring keyring, struct rpmsinfo_s *sinfo)
{
rpmRC res = RPMRC_FAIL;
if (pgpSignatureType(sinfo->sig) == PGPSIGTYPE_BINARY)
res = rpmKeyringVerifySig(keyring, sinfo->sig, sinfo->ctx);
return res;
}
static void
rpmVerifySignature(rpmKeyring keyring, struct rpmsinfo_s *sinfo)
{
if (sinfo->type == RPMSIG_DIGEST_TYPE)
sinfo->rc = verifyDigest(sinfo);
else if (sinfo->type == RPMSIG_SIGNATURE_TYPE)
sinfo->rc = verifySignature(keyring, sinfo);
else
sinfo->rc = RPMRC_FAIL;
}