diff options
Diffstat (limited to 'lib/rpmchecksig.c')
-rw-r--r-- | lib/rpmchecksig.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c new file mode 100644 index 0000000..850ca62 --- /dev/null +++ b/lib/rpmchecksig.c @@ -0,0 +1,469 @@ +/** \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 rpmTagVal bestSig(Header sigh, int nosignatures, int nodigests) +{ + rpmTagVal 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(rpmTagVal 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(rpmTagVal 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; + rpmTagVal sigtag; + pgpDig dig = NULL; + pgpDigParams sigp; + Header sigh = NULL; + HeaderIterator hi = NULL; + char * msg = NULL; + int res = 1; /* assume failure */ + 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) { + int xx = -1; + if (headerGet(sigh, sigtag, &sigtd, HEADERGET_DEFAULT)) { + xx = pgpPrtPkts(sigtd.data, sigtd.count, dig, 0); + rpmtdFreeData(&sigtd); + } + if (xx) goto exit; + + /* 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; +} |