/** \ingroup rpmcli * \file lib/rpmchecksig.c * Verify the signature of a package. */ #include "system.h" #include /* RPMSIGTAG & related */ #include #include #include /* rpmMkTemp() */ #include #include #include #include #include #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 #. 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; }