From cb14ea4623079300c9fffb748a0f63518e64c1b5 Mon Sep 17 00:00:00 2001 From: Panu Matilainen Date: Tue, 4 Feb 2020 16:56:00 +0200 Subject: [PATCH] Add support for multiple, named OpenPGP signatures per package Add support for multiple, named signatures per package. Named signatures are stored as a encoded string array in RPMTAG_OPENPGPHEADER, name is an arbitrary \0-terminated string and is part of the signed data (appended after actual package data). These are always header-only signatures, and can co-exist with "traditional" signatures: if no name is specified when signing then the new signature replaces any old traditional signature the package may have. If name is specified then the signature is merely appended to RPMTAG_OPENPGPHEADER. Use: rpmsign --addsign --name= Not implemented yet: - name sanity checks (limit to alphanumeric and punctuation?) - named signatures are not checked for duplicates when signing - there's no way to delete just one named signature - ... Misc notes: We could trivially support signatures without names too in the same tag, but the question then becomes what to do with "traditional" signatures when name isn't specified - only putting named signatures into the new tag makes that simple(r). People will still need those legacy signatures for some time due to older versions not supporting the new. --- lib/package.c | 1 + lib/rpmtag.h | 2 ++ lib/rpmvs.c | 53 ++++++++++++++++++++++++++--- lib/rpmvs.h | 1 + rpmsign.c | 6 ++++ sign/rpmgensig.c | 79 ++++++++++++++++++++++++++++++++------------ sign/rpmsign.h | 1 + tests/rpmgeneral.at | 1 + tests/rpmsigdig.at | 38 +++++++++++++++++++++ tests/rpmvfylevel.at | 1 + 10 files changed, 157 insertions(+), 26 deletions(-) diff --git a/lib/package.c b/lib/package.c index db70d13f86..175be20dbb 100644 --- a/lib/package.c +++ b/lib/package.c @@ -75,6 +75,7 @@ void headerMergeLegacySigs(Header h, Header sigh) case RPMSIGTAG_SHA256: case RPMSIGTAG_DSA: case RPMSIGTAG_RSA: + case RPMSIGTAG_OPENPGP: default: if (!(td.tag >= HEADER_SIGBASE && td.tag < HEADER_TAGBASE)) continue; diff --git a/lib/rpmtag.h b/lib/rpmtag.h index 5618b7d30f..dc2d4c051f 100644 --- a/lib/rpmtag.h +++ b/lib/rpmtag.h @@ -67,6 +67,7 @@ typedef enum rpmTag_e { RPMTAG_SHA256HEADER = RPMTAG_SIG_BASE+17, /* s */ /* RPMTAG_SIG_BASE+18 reserved for RPMSIGTAG_FILESIGNATURES */ /* RPMTAG_SIG_BASE+19 reserved for RPMSIGTAG_FILESIGNATURELENGTH */ + RPMTAG_OPENPGPHEADER = RPMTAG_SIG_BASE+20, /* s[] */ RPMTAG_NAME = 1000, /* s */ #define RPMTAG_N RPMTAG_NAME /* s */ @@ -430,6 +431,7 @@ typedef enum rpmSigTag_e { RPMSIGTAG_SHA256 = RPMTAG_SHA256HEADER, RPMSIGTAG_FILESIGNATURES = RPMTAG_SIG_BASE + 18, RPMSIGTAG_FILESIGNATURELENGTH = RPMTAG_SIG_BASE + 19, + RPMSIGTAG_OPENPGP = RPMTAG_OPENPGPHEADER, } rpmSigTag; diff --git a/lib/rpmvs.c b/lib/rpmvs.c index 7255069fc6..8117691e16 100644 --- a/lib/rpmvs.c +++ b/lib/rpmvs.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "lib/rpmvs.h" #include "rpmio/digest.h" @@ -41,6 +42,7 @@ static const struct vfytag_s rpmvfytags[] = { { RPMTAG_SHA256HEADER, RPM_STRING_TYPE, 1, 65, }, { RPMTAG_PAYLOADDIGEST, RPM_STRING_ARRAY_TYPE, 0, 0, }, { RPMTAG_PAYLOADDIGESTALT, RPM_STRING_ARRAY_TYPE, 0, 0, }, + { RPMTAG_OPENPGPHEADER, RPM_STRING_ARRAY_TYPE, 0, 0, }, { 0 } /* sentinel */ }; @@ -93,6 +95,9 @@ static const struct vfyinfo_s rpmvfyitems[] = { { RPMTAG_PAYLOADDIGESTALT, 0, { RPMSIG_DIGEST_TYPE, RPMVSF_NOPAYLOAD, (RPMSIG_PAYLOAD), PGPHASHALGO_SHA256, 0, 1, }, }, + { RPMTAG_OPENPGPHEADER, 1, + { RPMSIG_SIGNATURE_TYPE, 0 /* XXX FIXME disabler */, + (RPMSIG_HEADER), 0, 0, }, }, { 0 } /* sentinel */ }; @@ -140,6 +145,8 @@ static void rpmsinfoInit(const struct vfyinfo_s *vinfo, rpmRC rc = RPMRC_FAIL; const void *data = NULL; rpm_count_t dlen = 0; + uint8_t *pkt = NULL; + size_t pktlen = 0; *sinfo = vinfo->vi; /* struct assignment */ sinfo->wrapped = (vinfo->sigh == 0); @@ -175,6 +182,22 @@ static void rpmsinfoInit(const struct vfyinfo_s *vinfo, break; } + if (td->tag == RPMTAG_OPENPGPHEADER) { + const char *split = strchr(data, ':'); + if (split) { + /* Grab a copy of name */ + size_t nlen = split - (const char *)data; + sinfo->name = memcpy(xmalloc(nlen + 1), data, nlen); + sinfo->name[nlen] = '\0'; + /* Adjust data to beginning of hex part */ + data = split + 1; + dlen -= nlen + 1; + } else { + /* XXX: Should we permit missing nole? */ + data = NULL; + } + } + /* 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)"), @@ -193,6 +216,16 @@ static void rpmsinfoInit(const struct vfyinfo_s *vinfo, } if (sinfo->type == RPMSIG_SIGNATURE_TYPE) { + if (td->type == RPM_STRING_ARRAY_TYPE) { + if (rpmBase64Decode(data, (void **)&pkt, &pktlen)) { + rasprintf(&sinfo->msg, _("%s tag %u: invalid base64"), + origin, td->tag); + goto exit; + } + data = pkt; + dlen = pktlen; + } + if (pgpPrtParams(data, dlen, PGPTAG_SIGNATURE, &sinfo->sig)) { rasprintf(&sinfo->msg, _("%s tag %u: invalid OpenPGP signature"), origin, td->tag); @@ -219,6 +252,8 @@ static void rpmsinfoInit(const struct vfyinfo_s *vinfo, rc = RPMRC_OK; exit: + if (pkt && pkt != td->data) + free(pkt); sinfo->rc = rc; return; } @@ -233,6 +268,7 @@ static void rpmsinfoFini(struct rpmsinfo_s *sinfo) rpmDigestFinal(sinfo->ctx, NULL, NULL, 0); free(sinfo->msg); free(sinfo->descr); + free(sinfo->name); memset(sinfo, 0, sizeof(*sinfo)); } } @@ -270,13 +306,20 @@ const char *rpmsinfoDescr(struct rpmsinfo_s *sinfo) case RPMSIG_SIGNATURE_TYPE: if (sinfo->sig) { char *t = pgpIdentItem(sinfo->sig); - rasprintf(&sinfo->descr, _("%s%s"), - rangeName(sinfo->range), t); + if (sinfo->name) { + rasprintf(&sinfo->descr, _("%s%s [%s]"), + rangeName(sinfo->range), t, sinfo->name); + } else { + rasprintf(&sinfo->descr, _("%s%s"), + rangeName(sinfo->range), t); + } free(t); } else { + const char *alg = sinfo->sigalgo ? + pgpValString(PGPVAL_PUBKEYALGO, sinfo->sigalgo) : + "OpenPGP"; rasprintf(&sinfo->descr, _("%s%s%s %s"), - rangeName(sinfo->range), - pgpValString(PGPVAL_PUBKEYALGO, sinfo->sigalgo), + rangeName(sinfo->range), alg, sinfo->alt ? " ALT" : "", _("signature")); } @@ -394,6 +437,8 @@ void rpmvsFiniRange(struct rpmvs_s *sis, int range) if (sinfo->range == range && sinfo->rc == RPMRC_OK) { sinfo->ctx = rpmDigestBundleDupCtx(sis->bundle, sinfo->id); + if (sinfo->name) + rpmDigestUpdate(sinfo->ctx, sinfo->name, strlen(sinfo->name)+1); /* Handle unsupported digests the same as disabled ones */ if (sinfo->ctx == NULL) sinfo->rc = RPMRC_NOTFOUND; diff --git a/lib/rpmvs.h b/lib/rpmvs.h index e0ce16535d..c456430bfe 100644 --- a/lib/rpmvs.h +++ b/lib/rpmvs.h @@ -29,6 +29,7 @@ struct rpmsinfo_s { char *dig; }; char *descr; + char *name; DIGEST_CTX ctx; /* verify results */ rpmRC rc; diff --git a/rpmsign.c b/rpmsign.c index 1a5cd59c28..113ab9cb98 100644 --- a/rpmsign.c +++ b/rpmsign.c @@ -22,6 +22,7 @@ static int mode = MODE_NONE; static int signfiles = 0, fskpass = 0; static char * fileSigningKey = NULL; #endif +static char * name = NULL; static struct rpmSignArgs sargs = {NULL, 0, 0}; @@ -32,6 +33,8 @@ static struct poptOption signOptsTable[] = { N_("sign package(s) (identical to --addsign)"), NULL }, { "delsign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_DELSIGN, N_("delete package signatures"), NULL }, + { "name", '\0', POPT_ARG_STRING, &name, 0, + N_("signature name"), NULL }, #ifdef WITH_IMAEVM { "signfiles", '\0', POPT_ARG_NONE, &signfiles, 0, N_("sign package(s) files"), NULL}, @@ -168,6 +171,9 @@ int main(int argc, char *argv[]) } #endif + if (name) + sargs.name = name; + switch (mode) { case MODE_ADDSIGN: case MODE_RESIGN: diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c index 5be5420017..3248c447e6 100644 --- a/sign/rpmgensig.c +++ b/sign/rpmgensig.c @@ -17,6 +17,7 @@ #include /* rpmMkTemp() */ #include #include +#include #include #include "lib/rpmlead.h" @@ -191,7 +192,8 @@ static int copyFile(FD_t *sfdp, const char *sfnp, * generated signature is something we can use. * Return generated signature tag data on success, NULL on failure. */ -static rpmtd makeSigTag(Header sigh, int ishdr, uint8_t *pkt, size_t pktlen) +static rpmtd makeSigTag(Header sigh, int ishdr, uint8_t *pkt, size_t pktlen, + const char *name) { pgpDigParams sigp = NULL; rpmTagVal sigtag; @@ -225,20 +227,35 @@ static rpmtd makeSigTag(Header sigh, int ishdr, uint8_t *pkt, size_t pktlen) break; } + if (name) + sigtag = RPMSIGTAG_OPENPGP; + /* Looks sane, create the tag data */ sigtd = rpmtdNew(); - sigtd->count = pktlen; - sigtd->data = memcpy(xmalloc(pktlen), pkt, pktlen);; - sigtd->type = RPM_BIN_TYPE; sigtd->tag = sigtag; sigtd->flags |= RPMTD_ALLOCED; + if (name) { + char *b64 = rpmBase64Encode(pkt, pktlen, 0); + char *sigstr = rstrscat(NULL, name, ":", b64, NULL); + char **arr = xmalloc(1 * sizeof(*arr)); + arr[0] = sigstr; + sigtd->data = arr; + sigtd->count = 1; + sigtd->type = RPM_STRING_ARRAY_TYPE; + free(b64); + } else { + sigtd->count = pktlen; + sigtd->data = memcpy(xmalloc(pktlen), pkt, pktlen);; + sigtd->type = RPM_BIN_TYPE; + } + exit: pgpDigParamsFree(sigp); return sigtd; } -static int runGPG(sigTarget sigt, const char *sigfile) +static int runGPG(sigTarget sigt, const char *sigfile, const char *name) { int pid = 0, status; FD_t fnamedPipe = NULL; @@ -304,6 +321,15 @@ static int runGPG(sigTarget sigt, const char *sigfile) sigt->fileName, Fstrerror(sigt->fd)); goto exit; } + + if (name) { + size_t nlen = strlen(name) + 1; /* include trailing \0 */ + if (Fwrite(name, 1, nlen, fnamedPipe) != nlen) { + rpmlog(RPMLOG_ERR, _("Could not write to pipe\n")); + goto exit; + } + } + Fclose(fnamedPipe); fnamedPipe = NULL; @@ -337,9 +363,11 @@ static int runGPG(sigTarget sigt, const char *sigfile) * @param ishdr header-only signature? * @param sigt signature target * @param passPhrase private key pass phrase + * @param name signer name (or NULL) * @return generated sigtag on success, 0 on failure */ -static rpmtd makeGPGSignature(Header sigh, int ishdr, sigTarget sigt) +static rpmtd makeGPGSignature(Header sigh, int ishdr, sigTarget sigt, + const char *name) { char * sigfile = rstrscat(NULL, sigt->fileName, ".sig", NULL); struct stat st; @@ -347,7 +375,7 @@ static rpmtd makeGPGSignature(Header sigh, int ishdr, sigTarget sigt) size_t pktlen = 0; rpmtd sigtd = NULL; - if (runGPG(sigt, sigfile)) + if (runGPG(sigt, sigfile, name)) goto exit; if (stat(sigfile, &st)) { @@ -377,7 +405,7 @@ static rpmtd makeGPGSignature(Header sigh, int ishdr, sigTarget sigt) rpmlog(RPMLOG_DEBUG, "Got %zd bytes of GPG sig\n", pktlen); /* Parse the signature, change signature tag as appropriate. */ - sigtd = makeSigTag(sigh, ishdr, pkt, pktlen); + sigtd = makeSigTag(sigh, ishdr, pkt, pktlen, name); exit: (void) unlink(sigfile); free(sigfile); @@ -393,6 +421,7 @@ static void deleteSigs(Header sigh) headerDel(sigh, RPMSIGTAG_DSA); headerDel(sigh, RPMSIGTAG_RSA); headerDel(sigh, RPMSIGTAG_PGP5); + headerDel(sigh, RPMSIGTAG_OPENPGP); } static int haveSignature(rpmtd sigtd, Header h) @@ -418,13 +447,15 @@ static int haveSignature(rpmtd sigtd, Header h) return rc; } -static int replaceSignature(Header sigh, sigTarget sigt_v3, sigTarget sigt_v4) +static int replaceSignature(Header sigh, sigTarget sigt_v3, sigTarget sigt_v4, + const char *name) { int rc = -1; rpmtd sigtd = NULL; + int mode = name ? HEADERPUT_APPEND : HEADERPUT_DEFAULT; /* Make the cheaper v4 signature first */ - if ((sigtd = makeGPGSignature(sigh, 1, sigt_v4)) == NULL) + if ((sigtd = makeGPGSignature(sigh, 1, sigt_v4, name)) == NULL) goto exit; /* See if we already have a signature by the same key and parameters */ @@ -433,18 +464,22 @@ static int replaceSignature(Header sigh, sigTarget sigt_v3, sigTarget sigt_v4) goto exit; } /* Nuke all signature tags */ - deleteSigs(sigh); + if (!name) + deleteSigs(sigh); - if (headerPut(sigh, sigtd, HEADERPUT_DEFAULT) == 0) + if (headerPut(sigh, sigtd, mode) == 0) goto exit; - rpmtdFree(sigtd); - /* Assume the same signature test holds for v3 signature too */ - if ((sigtd = makeGPGSignature(sigh, 0, sigt_v3)) == NULL) - goto exit; + if (!name) { + rpmtdFree(sigtd); - if (headerPut(sigh, sigtd, HEADERPUT_DEFAULT) == 0) - goto exit; + /* Assume the same signature test holds for v3 signature too */ + if ((sigtd = makeGPGSignature(sigh, 0, sigt_v3, NULL)) == NULL) + goto exit; + + if (headerPut(sigh, sigtd, mode) == 0) + goto exit; + } rc = 0; exit: @@ -522,7 +557,7 @@ static int checkPkg(FD_t fd, char **msg) * @param signfiles sign files if non-zero * @return 0 on success, -1 on error */ -static int rpmSign(const char *rpm, int deleting, int signfiles) +static int rpmSign(const char *rpm, int deleting, int signfiles, const char *name) { FD_t fd = NULL; FD_t ofd = NULL; @@ -596,7 +631,7 @@ static int rpmSign(const char *rpm, int deleting, int signfiles) sigt_v4 = sigt_v3; sigt_v4.size = headerSizeof(h, HEADER_MAGIC_YES); - res = replaceSignature(sigh, &sigt_v3, &sigt_v4); + res = replaceSignature(sigh, &sigt_v3, &sigt_v4, name); if (res != 0) { if (res == 1) { rpmlog(RPMLOG_WARNING, @@ -716,7 +751,7 @@ int rpmPkgSign(const char *path, const struct rpmSignArgs * args) } } - rc = rpmSign(path, 0, args ? args->signfiles : 0); + rc = rpmSign(path, 0, args ? args->signfiles : 0, args ? args->name : NULL); if (args) { if (args->hashalgo) { @@ -732,5 +767,5 @@ int rpmPkgSign(const char *path, const struct rpmSignArgs * args) int rpmPkgDelSign(const char *path, const struct rpmSignArgs * args) { - return rpmSign(path, 1, 0); + return rpmSign(path, 1, 0, NULL); } diff --git a/sign/rpmsign.h b/sign/rpmsign.h index bed8d6245d..f64b9c3f37 100644 --- a/sign/rpmsign.h +++ b/sign/rpmsign.h @@ -17,6 +17,7 @@ struct rpmSignArgs { char *keyid; pgpHashAlgo hashalgo; int signfiles; + char *name; /* ... what else? */ }; diff --git a/tests/rpmgeneral.at b/tests/rpmgeneral.at index 1b7ca452aa..986de2e8e7 100644 --- a/tests/rpmgeneral.at +++ b/tests/rpmgeneral.at @@ -174,6 +174,7 @@ OLDSUGGESTS OLDSUGGESTSFLAGS OLDSUGGESTSNAME OLDSUGGESTSVERSION +OPENPGPHEADER OPTFLAGS ORDERFLAGS ORDERNAME diff --git a/tests/rpmsigdig.at b/tests/rpmsigdig.at index aa1928d8ea..005f3efa6f 100644 --- a/tests/rpmsigdig.at +++ b/tests/rpmsigdig.at @@ -470,6 +470,44 @@ POST-DELSIGN []) AT_CLEANUP +AT_SETUP([rpmsign --addsign --name ]) +AT_KEYWORDS([rpmsign signature]) +AT_CHECK([ +RPMDB_CLEAR +RPMDB_INIT +rm -rf "${TOPDIR}" + +cp "${RPMTEST}"/data/RPMS/hello-2.0-1.x86_64.rpm "${RPMTEST}"/tmp/ +run rpmsign --key-id 1964C5FC --addsign --name "org.rpm.test" "${RPMTEST}"/tmp/hello-2.0-1.x86_64.rpm > /dev/null +echo PRE-IMPORT1 +runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64.rpm|grep -v digest +run rpmsign --key-id 1964C5FC --addsign --name "org.rpm.qe" "${RPMTEST}"/tmp/hello-2.0-1.x86_64.rpm > /dev/null +echo PRE-IMPORT2 +runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64.rpm|grep -v digest +echo POST-IMPORT +runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64.rpm|grep -v digest +run rpmsign --delsign "${RPMTEST}"/tmp/hello-2.0-1.x86_64.rpm > /dev/null +echo POST-DELSIGN +runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64.rpm|grep -v digest +], +[0], +[[PRE-IMPORT1 +/tmp/hello-2.0-1.x86_64.rpm: + Header V4 RSA/SHA256 Signature, key ID 1964c5fc [org.rpm.test]: NOKEY +PRE-IMPORT2 +/tmp/hello-2.0-1.x86_64.rpm: + Header V4 RSA/SHA256 Signature, key ID 1964c5fc [org.rpm.qe]: NOKEY + Header V4 RSA/SHA256 Signature, key ID 1964c5fc [org.rpm.test]: NOKEY +POST-IMPORT +/tmp/hello-2.0-1.x86_64.rpm: + Header V4 RSA/SHA256 Signature, key ID 1964c5fc [org.rpm.qe]: OK + Header V4 RSA/SHA256 Signature, key ID 1964c5fc [org.rpm.test]: OK +POST-DELSIGN +/tmp/hello-2.0-1.x86_64.rpm: +]], +[]) +AT_CLEANUP # ------------------------------ # Test --delsign AT_SETUP([rpmsign --delsign ]) diff --git a/tests/rpmvfylevel.at b/tests/rpmvfylevel.at index cb5e48ad7e..da403ec8df 100644 --- a/tests/rpmvfylevel.at +++ b/tests/rpmvfylevel.at @@ -356,6 +356,7 @@ nosig /data/RPMS/hello-2.0-1.x86_64-signed.rpm: Header RSA signature: NOTFOUND Header DSA signature: NOTFOUND + Header OpenPGP signature: NOTFOUND Header SHA256 digest: OK Header SHA1 digest: OK Payload SHA256 digest: OK