5eb85a7a5e
Some of the preceding code is probably undefined or unspecified behavior, but there's no easy way to fix it other than rewriting, which I'm not going to do. Surprisingly enough, the code just happens to work, due to a series of mutual cancellations mod 2^32. As they say in Russian, the war will write off all. Likewise, mod 2^32 arithmetic can write off a multitude of sins (James 5:20). > static inline rpmRC checkSize(FD_t fd, int siglen, int pad, int datalen) [...] > int delta; [...] > delta = (sizeof(struct rpmlead) + siglen + pad + datalen) - st.st_size; Here, the expression in parentheses yields a different numeric value depending on whether datalen is signed or unsigned. However, when delta is finally truncated to 32 bits, the result turns out to be the same. > switch (delta) { > case -32: /* XXX rpm-4.0 packages */ > case 32: /* XXX Legacy headers have a HEADER_IMAGE tag added. */ > case 0:
1003 lines
26 KiB
C
1003 lines
26 KiB
C
/** \ingroup signature
|
|
* \file lib/signature.c
|
|
*/
|
|
|
|
/* signature.c - RPM signature functions */
|
|
|
|
/* NOTES
|
|
*
|
|
* Things have been cleaned up wrt PGP. We can now handle
|
|
* signatures of any length (which means you can use any
|
|
* size key you like). We also honor PGPPATH finally.
|
|
*/
|
|
|
|
#include "system.h"
|
|
|
|
#include "rpmio_internal.h"
|
|
#include "rpmlib.h"
|
|
#include "rpmmacro.h" /* XXX for rpmGetPath() */
|
|
|
|
#include "misc.h" /* XXX for dosetenv() and makeTempFile() */
|
|
#include "rpmlead.h"
|
|
#include "signature.h"
|
|
#include "debug.h"
|
|
|
|
/*@access Header@*/ /* XXX compared with NULL */
|
|
/*@access FD_t@*/ /* XXX compared with NULL */
|
|
|
|
typedef int (*md5func)(const char * fn, /*@out@*/ byte * digest);
|
|
|
|
int rpmLookupSignatureType(int action)
|
|
{
|
|
static int disabled = 0;
|
|
int rc = 0;
|
|
|
|
switch (action) {
|
|
case RPMLOOKUPSIG_DISABLE:
|
|
disabled = -2;
|
|
break;
|
|
case RPMLOOKUPSIG_ENABLE:
|
|
disabled = 0;
|
|
/*@fallthrough@*/
|
|
case RPMLOOKUPSIG_QUERY:
|
|
if (disabled)
|
|
break; /* Disabled */
|
|
{ const char *name = rpmExpand("%{?_signature}", NULL);
|
|
if (!(name && *name != '\0'))
|
|
rc = 0;
|
|
else if (!xstrcasecmp(name, "none"))
|
|
rc = 0;
|
|
else if (!xstrcasecmp(name, "pgp"))
|
|
rc = RPMSIGTAG_PGP;
|
|
else if (!xstrcasecmp(name, "pgp5")) /* XXX legacy */
|
|
rc = RPMSIGTAG_PGP;
|
|
else if (!xstrcasecmp(name, "gpg"))
|
|
rc = RPMSIGTAG_GPG;
|
|
else
|
|
rc = -1; /* Invalid %_signature spec in macro file */
|
|
name = _free(name);
|
|
} break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* rpmDetectPGPVersion() returns the absolute path to the "pgp" */
|
|
/* executable of the requested version, or NULL when none found. */
|
|
|
|
const char * rpmDetectPGPVersion(pgpVersion * pgpVer)
|
|
{
|
|
/* Actually this should support having more then one pgp version. */
|
|
/* At the moment only one version is possible since we only */
|
|
/* have one %_pgpbin and one %_pgp_path. */
|
|
|
|
static pgpVersion saved_pgp_version = PGP_UNKNOWN;
|
|
const char *pgpbin = rpmGetPath("%{?_pgpbin}", NULL);
|
|
|
|
if (saved_pgp_version == PGP_UNKNOWN) {
|
|
char *pgpvbin;
|
|
struct stat st;
|
|
|
|
if (!(pgpbin && pgpbin[0] != '\0')) {
|
|
pgpbin = _free(pgpbin);
|
|
saved_pgp_version = -1;
|
|
return NULL;
|
|
}
|
|
pgpvbin = (char *)alloca(strlen(pgpbin) + sizeof("v"));
|
|
(void)stpcpy(stpcpy(pgpvbin, pgpbin), "v");
|
|
|
|
if (stat(pgpvbin, &st) == 0)
|
|
saved_pgp_version = PGP_5;
|
|
else if (stat(pgpbin, &st) == 0)
|
|
saved_pgp_version = PGP_2;
|
|
else
|
|
saved_pgp_version = PGP_NOTDETECTED;
|
|
}
|
|
|
|
if (pgpVer && pgpbin)
|
|
*pgpVer = saved_pgp_version;
|
|
return pgpbin;
|
|
}
|
|
|
|
/**
|
|
* Check package size.
|
|
* @todo rpmio: use fdSize rather than fstat(2) to get file size.
|
|
* @param fd package file handle
|
|
* @param siglen signature header size
|
|
* @param pad signature padding
|
|
* @param datalen length of header+payload
|
|
* @return rpmRC return code
|
|
*/
|
|
static inline rpmRC checkSize(FD_t fd, int siglen, int pad, int datalen)
|
|
/*@globals fileSystem @*/
|
|
/*@modifies fileSystem @*/
|
|
{
|
|
struct stat st;
|
|
int delta;
|
|
rpmRC rc;
|
|
|
|
if (fstat(Fileno(fd), &st))
|
|
return RPMRC_FAIL;
|
|
|
|
if (!S_ISREG(st.st_mode)) {
|
|
rpmMessage(RPMMESS_DEBUG,
|
|
_("file is not regular -- skipping size check\n"));
|
|
return RPMRC_OK;
|
|
}
|
|
|
|
delta = (sizeof(struct rpmlead) + siglen + pad + datalen) - st.st_size;
|
|
switch (delta) {
|
|
case -32: /* XXX rpm-4.0 packages */
|
|
case 32: /* XXX Legacy headers have a HEADER_IMAGE tag added. */
|
|
case 0:
|
|
rc = RPMRC_OK;
|
|
break;
|
|
default:
|
|
rc = RPMRC_BADSIZE;
|
|
break;
|
|
}
|
|
|
|
rpmMessage((rc == RPMRC_OK ? RPMMESS_DEBUG : RPMMESS_WARNING),
|
|
_("Expected size: %12zu = lead(%zu)+sigs(%d)+pad(%d)+data(%u)\n"),
|
|
sizeof(struct rpmlead)+siglen+pad+datalen,
|
|
sizeof(struct rpmlead), siglen, pad, (unsigned)datalen);
|
|
rpmMessage((rc == RPMRC_OK ? RPMMESS_DEBUG : RPMMESS_WARNING),
|
|
_(" Actual size: %12zu\n"), (size_t)st.st_size);
|
|
|
|
return rc;
|
|
}
|
|
|
|
rpmRC rpmReadSignature(FD_t fd, Header * headerp, sigType sig_type)
|
|
{
|
|
byte buf[2048];
|
|
int sigSize, pad;
|
|
int_32 type, count;
|
|
int_32 *archSize;
|
|
Header h = NULL;
|
|
rpmRC rc = RPMRC_FAIL; /* assume failure */
|
|
|
|
if (headerp)
|
|
*headerp = NULL;
|
|
|
|
buf[0] = 0;
|
|
switch (sig_type) {
|
|
case RPMSIGTYPE_NONE:
|
|
rpmMessage(RPMMESS_DEBUG, _("No signature\n"));
|
|
rc = RPMRC_OK;
|
|
break;
|
|
case RPMSIGTYPE_PGP262_1024:
|
|
rpmMessage(RPMMESS_DEBUG, _("Old PGP signature\n"));
|
|
/* These are always 256 bytes */
|
|
if (timedRead(fd, buf, 256) != 256)
|
|
break;
|
|
h = headerNew();
|
|
(void) headerAddEntry(h, RPMSIGTAG_PGP, RPM_BIN_TYPE, buf, 152);
|
|
rc = RPMRC_OK;
|
|
break;
|
|
case RPMSIGTYPE_MD5:
|
|
case RPMSIGTYPE_MD5_PGP:
|
|
rpmError(RPMERR_BADSIGTYPE,
|
|
_("Old (internal-only) signature! How did you get that!?\n"));
|
|
break;
|
|
case RPMSIGTYPE_HEADERSIG:
|
|
case RPMSIGTYPE_DISABLE:
|
|
/* This is a new style signature */
|
|
h = headerRead(fd, HEADER_MAGIC_YES);
|
|
if (h == NULL)
|
|
break;
|
|
|
|
rc = RPMRC_OK;
|
|
sigSize = headerSizeof(h, HEADER_MAGIC_YES);
|
|
|
|
pad = (8 - (sigSize % 8)) % 8; /* 8-byte pad */
|
|
if (sig_type == RPMSIGTYPE_HEADERSIG) {
|
|
if (! headerGetEntry(h, RPMSIGTAG_SIZE, &type,
|
|
(void **)&archSize, &count))
|
|
break;
|
|
rc = checkSize(fd, sigSize, pad, *archSize);
|
|
}
|
|
if (pad && timedRead(fd, buf, pad) != pad)
|
|
rc = RPMRC_SHORTREAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (headerp && rc == RPMRC_OK)
|
|
*headerp = h;
|
|
else if (h)
|
|
h = headerFree(h);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int rpmWriteSignature(FD_t fd, Header h)
|
|
{
|
|
static byte buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int sigSize, pad;
|
|
int rc;
|
|
|
|
rc = headerWrite(fd, h, HEADER_MAGIC_YES);
|
|
if (rc)
|
|
return rc;
|
|
|
|
sigSize = headerSizeof(h, HEADER_MAGIC_YES);
|
|
pad = (8 - (sigSize % 8)) % 8;
|
|
if (pad) {
|
|
if (Fwrite(buf, sizeof(buf[0]), pad, fd) != pad)
|
|
rc = 1;
|
|
}
|
|
rpmMessage(RPMMESS_DEBUG, _("Signature: size(%d)+pad(%d)\n"), sigSize, pad);
|
|
return rc;
|
|
}
|
|
|
|
Header rpmNewSignature(void)
|
|
{
|
|
Header h = headerNew();
|
|
return h;
|
|
}
|
|
|
|
Header rpmFreeSignature(Header h)
|
|
{
|
|
return headerFree(h);
|
|
}
|
|
|
|
static int makePGPSignature(const char * file, /*@out@*/ void ** sig,
|
|
/*@out@*/ int_32 * size, /*@null@*/ const char * passPhrase)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies *sig, *size, rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
char * sigfile = alloca(1024);
|
|
int pid, status;
|
|
int inpipe[2];
|
|
struct stat st;
|
|
const char * cmd;
|
|
char *const *av;
|
|
int rc;
|
|
|
|
(void) stpcpy( stpcpy(sigfile, file), ".sig");
|
|
|
|
addMacro(NULL, "__plaintext_filename", NULL, file, -1);
|
|
addMacro(NULL, "__signature_filename", NULL, sigfile, -1);
|
|
|
|
inpipe[0] = inpipe[1] = 0;
|
|
(void) pipe(inpipe);
|
|
|
|
if (!(pid = fork())) {
|
|
const char *pgp_path = rpmExpand("%{?_pgp_path}", NULL);
|
|
const char *path;
|
|
pgpVersion pgpVer;
|
|
|
|
(void) close(STDIN_FILENO);
|
|
(void) dup2(inpipe[0], 3);
|
|
(void) close(inpipe[1]);
|
|
|
|
(void) dosetenv("PGPPASSFD", "3", 1);
|
|
if (pgp_path && *pgp_path != '\0')
|
|
(void) dosetenv("PGPPATH", pgp_path, 1);
|
|
|
|
/* dosetenv("PGPPASS", passPhrase, 1); */
|
|
|
|
if ((path = rpmDetectPGPVersion(&pgpVer)) != NULL) {
|
|
switch(pgpVer) {
|
|
case PGP_2:
|
|
cmd = rpmExpand("%{?__pgp_sign_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
break;
|
|
case PGP_5:
|
|
cmd = rpmExpand("%{?__pgp5_sign_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
break;
|
|
case PGP_UNKNOWN:
|
|
case PGP_NOTDETECTED:
|
|
errno = ENOENT;
|
|
break;
|
|
}
|
|
}
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "pgp",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
}
|
|
|
|
delMacro(NULL, "__plaintext_filename");
|
|
delMacro(NULL, "__signature_filename");
|
|
|
|
(void) close(inpipe[0]);
|
|
if (passPhrase)
|
|
(void) write(inpipe[1], passPhrase, strlen(passPhrase));
|
|
(void) write(inpipe[1], "\n", 1);
|
|
(void) close(inpipe[1]);
|
|
|
|
(void)waitpid(pid, &status, 0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
|
|
rpmError(RPMERR_SIGGEN, _("pgp failed\n"));
|
|
return 1;
|
|
}
|
|
|
|
if (stat(sigfile, &st)) {
|
|
/* PGP failed to write signature */
|
|
if (sigfile) (void) unlink(sigfile); /* Just in case */
|
|
rpmError(RPMERR_SIGGEN, _("pgp failed to write signature\n"));
|
|
return 1;
|
|
}
|
|
|
|
*size = st.st_size;
|
|
rpmMessage(RPMMESS_DEBUG, _("PGP sig size: %d\n"), *size);
|
|
*sig = xmalloc(*size);
|
|
|
|
{ FD_t fd;
|
|
rc = 0;
|
|
fd = Fopen(sigfile, "r.fdio");
|
|
if (fd != NULL && !Ferror(fd)) {
|
|
rc = timedRead(fd, *sig, *size);
|
|
if (sigfile) (void) unlink(sigfile);
|
|
(void) Fclose(fd);
|
|
}
|
|
if (rc != *size) {
|
|
*sig = _free(*sig);
|
|
rpmError(RPMERR_SIGGEN, _("unable to read the signature\n"));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
rpmMessage(RPMMESS_DEBUG, _("Got %d bytes of PGP sig\n"), *size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This is an adaptation of the makePGPSignature function to use GPG instead
|
|
* of PGP to create signatures. I think I've made all the changes necessary,
|
|
* but this could be a good place to start looking if errors in GPG signature
|
|
* creation crop up.
|
|
*/
|
|
static int makeGPGSignature(const char * file, /*@out@*/ void ** sig,
|
|
/*@out@*/ int_32 * size, /*@null@*/ const char * passPhrase)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies *sig, *size, rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
char * sigfile = alloca(1024);
|
|
int pid, status;
|
|
int inpipe[2];
|
|
FILE * fpipe;
|
|
struct stat st;
|
|
const char * cmd;
|
|
char *const *av;
|
|
int rc;
|
|
|
|
(void) stpcpy( stpcpy(sigfile, file), ".sig");
|
|
|
|
addMacro(NULL, "__plaintext_filename", NULL, file, -1);
|
|
addMacro(NULL, "__signature_filename", NULL, sigfile, -1);
|
|
|
|
inpipe[0] = inpipe[1] = 0;
|
|
(void) pipe(inpipe);
|
|
|
|
if (!(pid = fork())) {
|
|
const char *gpg_path = rpmExpand("%{?_gpg_path}", NULL);
|
|
|
|
(void) close(STDIN_FILENO);
|
|
(void) dup2(inpipe[0], 3);
|
|
(void) close(inpipe[1]);
|
|
|
|
if (gpg_path && *gpg_path != '\0')
|
|
(void) dosetenv("GNUPGHOME", gpg_path, 1);
|
|
|
|
cmd = rpmExpand("%{?__gpg_sign_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "gpg",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
}
|
|
|
|
delMacro(NULL, "__plaintext_filename");
|
|
delMacro(NULL, "__signature_filename");
|
|
|
|
fpipe = fdopen(inpipe[1], "w");
|
|
(void) close(inpipe[0]);
|
|
if (fpipe) {
|
|
fprintf(fpipe, "%s\n", (passPhrase ? passPhrase : ""));
|
|
(void) fclose(fpipe);
|
|
}
|
|
|
|
(void)waitpid(pid, &status, 0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
|
|
rpmError(RPMERR_SIGGEN, _("gpg failed\n"));
|
|
return 1;
|
|
}
|
|
|
|
if (stat(sigfile, &st)) {
|
|
/* GPG failed to write signature */
|
|
if (sigfile) (void) unlink(sigfile); /* Just in case */
|
|
rpmError(RPMERR_SIGGEN, _("gpg failed to write signature\n"));
|
|
return 1;
|
|
}
|
|
|
|
*size = st.st_size;
|
|
rpmMessage(RPMMESS_DEBUG, _("GPG sig size: %d\n"), *size);
|
|
*sig = xmalloc(*size);
|
|
|
|
{ FD_t fd;
|
|
int rc = 0;
|
|
fd = Fopen(sigfile, "r.fdio");
|
|
if (fd != NULL && !Ferror(fd)) {
|
|
rc = timedRead(fd, *sig, *size);
|
|
if (sigfile) (void) unlink(sigfile);
|
|
(void) Fclose(fd);
|
|
}
|
|
if (rc != *size) {
|
|
*sig = _free(*sig);
|
|
rpmError(RPMERR_SIGGEN, _("unable to read the signature\n"));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
rpmMessage(RPMMESS_DEBUG, _("Got %d bytes of GPG sig\n"), *size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rpmAddSignature(Header h, const char * file, int_32 sigTag,
|
|
const char *passPhrase)
|
|
{
|
|
struct stat st;
|
|
int_32 size;
|
|
byte buf[16];
|
|
void *sig;
|
|
int ret = -1;
|
|
|
|
switch (sigTag) {
|
|
case RPMSIGTAG_SIZE:
|
|
(void) stat(file, &st);
|
|
size = st.st_size;
|
|
ret = 0;
|
|
(void) headerAddEntry(h, RPMSIGTAG_SIZE, RPM_INT32_TYPE, &size, 1);
|
|
break;
|
|
case RPMSIGTAG_MD5:
|
|
ret = mdbinfile(file, buf);
|
|
if (ret == 0)
|
|
(void) headerAddEntry(h, sigTag, RPM_BIN_TYPE, buf, 16);
|
|
break;
|
|
case RPMSIGTAG_PGP5: /* XXX legacy */
|
|
case RPMSIGTAG_PGP:
|
|
rpmMessage(RPMMESS_VERBOSE, _("Generating signature using PGP.\n"));
|
|
ret = makePGPSignature(file, &sig, &size, passPhrase);
|
|
if (ret == 0)
|
|
(void) headerAddEntry(h, sigTag, RPM_BIN_TYPE, sig, size);
|
|
break;
|
|
case RPMSIGTAG_GPG:
|
|
rpmMessage(RPMMESS_VERBOSE, _("Generating signature using GPG.\n"));
|
|
ret = makeGPGSignature(file, &sig, &size, passPhrase);
|
|
if (ret == 0)
|
|
(void) headerAddEntry(h, sigTag, RPM_BIN_TYPE, sig, size);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static rpmVerifySignatureReturn
|
|
verifySizeSignature(const char * datafile, int_32 size, /*@out@*/ char * result)
|
|
/*@globals fileSystem @*/
|
|
/*@modifies *result, fileSystem @*/
|
|
{
|
|
struct stat st;
|
|
|
|
(void) stat(datafile, &st);
|
|
if (size != st.st_size) {
|
|
sprintf(result, "Header+Archive size mismatch.\n"
|
|
"Expected %d, saw %d.\n",
|
|
size, (int)st.st_size);
|
|
return RPMSIG_BAD;
|
|
}
|
|
|
|
sprintf(result, "Header+Archive size OK: %d bytes\n", size);
|
|
return RPMSIG_OK;
|
|
}
|
|
|
|
#define X(_x) (unsigned)((_x) & 0xff)
|
|
|
|
static rpmVerifySignatureReturn
|
|
verifyMD5Signature(const char * datafile, const byte * sig,
|
|
/*@out@*/ char * result, md5func fn)
|
|
/*@globals fileSystem @*/
|
|
/*@modifies *result, fileSystem @*/
|
|
{
|
|
byte md5sum[16];
|
|
|
|
memset(md5sum, 0, sizeof(md5sum));
|
|
(void) fn(datafile, md5sum);
|
|
if (memcmp(md5sum, sig, 16)) {
|
|
sprintf(result, "MD5 sum mismatch\n"
|
|
"Expected: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
|
"%02x%02x%02x%02x%02x\n"
|
|
"Saw : %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
|
"%02x%02x%02x%02x%02x\n",
|
|
X(sig[0]), X(sig[1]), X(sig[2]), X(sig[3]),
|
|
X(sig[4]), X(sig[5]), X(sig[6]), X(sig[7]),
|
|
X(sig[8]), X(sig[9]), X(sig[10]), X(sig[11]),
|
|
X(sig[12]), X(sig[13]), X(sig[14]), X(sig[15]),
|
|
X(md5sum[0]), X(md5sum[1]), X(md5sum[2]), X(md5sum[3]),
|
|
X(md5sum[4]), X(md5sum[5]), X(md5sum[6]), X(md5sum[7]),
|
|
X(md5sum[8]), X(md5sum[9]), X(md5sum[10]), X(md5sum[11]),
|
|
X(md5sum[12]), X(md5sum[13]), X(md5sum[14]), X(md5sum[15]) );
|
|
return RPMSIG_BAD;
|
|
}
|
|
|
|
sprintf(result, "MD5 sum OK: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
|
"%02x%02x%02x%02x%02x\n",
|
|
X(md5sum[0]), X(md5sum[1]), X(md5sum[2]), X(md5sum[3]),
|
|
X(md5sum[4]), X(md5sum[5]), X(md5sum[6]), X(md5sum[7]),
|
|
X(md5sum[8]), X(md5sum[9]), X(md5sum[10]), X(md5sum[11]),
|
|
X(md5sum[12]), X(md5sum[13]), X(md5sum[14]), X(md5sum[15]) );
|
|
|
|
return RPMSIG_OK;
|
|
}
|
|
|
|
static rpmVerifySignatureReturn
|
|
verifyPGPSignature(const char * datafile, const void * sig, int count,
|
|
/*@out@*/ char * result)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies *result, rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
int pid, status, outpipe[2];
|
|
/*@only@*/ /*@null@*/ const char * sigfile = NULL;
|
|
byte buf[BUFSIZ];
|
|
FILE *file;
|
|
int res = RPMSIG_OK;
|
|
const char *path;
|
|
pgpVersion pgpVer;
|
|
const char * cmd;
|
|
char *const *av;
|
|
int rc;
|
|
|
|
/* What version do we have? */
|
|
if ((path = rpmDetectPGPVersion(&pgpVer)) == NULL) {
|
|
errno = ENOENT;
|
|
rpmError(RPMERR_EXEC, ("Could not exec %s: %s\n"), "pgp",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
}
|
|
|
|
/*
|
|
* Sad but true: pgp-5.0 returns exit value of 0 on bad signature.
|
|
* Instead we have to use the text output to detect a bad signature.
|
|
*/
|
|
if (pgpVer == PGP_5)
|
|
res = RPMSIG_BAD;
|
|
|
|
/* Write out the signature */
|
|
#ifdef DYING
|
|
{ const char *tmppath = rpmGetPath("%{_tmppath}", NULL);
|
|
sigfile = tempnam(tmppath, "rpmsig");
|
|
tmppath = _free(tmppath);
|
|
}
|
|
sfd = Fopen(sigfile, "w.fdio");
|
|
if (sfd != NULL && !Ferror(sfd)) {
|
|
(void) Fwrite(sig, sizeof(char), count, sfd);
|
|
(void) Fclose(sfd);
|
|
}
|
|
#else
|
|
{ FD_t sfd;
|
|
if (!makeTempFile(NULL, &sigfile, &sfd)) {
|
|
(void) Fwrite(sig, sizeof(char), count, sfd);
|
|
(void) Fclose(sfd);
|
|
sfd = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
if (sigfile == NULL)
|
|
return RPMSIG_BAD;
|
|
|
|
addMacro(NULL, "__plaintext_filename", NULL, datafile, -1);
|
|
addMacro(NULL, "__signature_filename", NULL, sigfile, -1);
|
|
|
|
/* Now run PGP */
|
|
outpipe[0] = outpipe[1] = 0;
|
|
(void) pipe(outpipe);
|
|
|
|
if (!(pid = fork())) {
|
|
const char *pgp_path = rpmExpand("%{?_pgp_path}", NULL);
|
|
|
|
(void) close(outpipe[0]);
|
|
(void) close(STDOUT_FILENO); /* XXX unnecessary */
|
|
(void) dup2(outpipe[1], STDOUT_FILENO);
|
|
|
|
if (pgp_path && *pgp_path != '\0')
|
|
(void) dosetenv("PGPPATH", pgp_path, 1);
|
|
|
|
switch (pgpVer) {
|
|
case PGP_2:
|
|
cmd = rpmExpand("%{?__pgp_verify_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
break;
|
|
case PGP_5:
|
|
/* Some output (in particular "This signature applies to */
|
|
/* another message") is _always_ written to stderr; we */
|
|
/* want to catch that output, so dup stdout to stderr: */
|
|
{ int save_stderr = dup(2);
|
|
(void) dup2(1, 2);
|
|
|
|
cmd = rpmExpand("%{?__pgp5_verify_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
|
|
/* Restore stderr so we can print the error message below. */
|
|
(void) dup2(save_stderr, 2);
|
|
(void) close(save_stderr);
|
|
} break;
|
|
case PGP_UNKNOWN:
|
|
case PGP_NOTDETECTED:
|
|
break;
|
|
}
|
|
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "pgp",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
}
|
|
|
|
delMacro(NULL, "__plaintext_filename");
|
|
delMacro(NULL, "__signature_filename");
|
|
|
|
(void) close(outpipe[1]);
|
|
file = fdopen(outpipe[0], "r");
|
|
result[0] = '\0';
|
|
if (file) {
|
|
char *t = result;
|
|
int nb = 8*BUFSIZ - 1;
|
|
while (fgets(buf, 1024, file)) {
|
|
if (strncmp("File '", buf, 6) &&
|
|
strncmp("Text is assu", buf, 12) &&
|
|
strncmp("This signature applies to another message", buf, 41) &&
|
|
buf[0] != '\n') {
|
|
nb -= strlen(buf);
|
|
if (nb > 0) t = stpncpy(t, buf, nb);
|
|
}
|
|
if (!strncmp("WARNING: Can't find the right public key", buf, 40))
|
|
res = RPMSIG_NOKEY;
|
|
else if (!strncmp("Signature by unknown keyid:", buf, 27))
|
|
res = RPMSIG_NOKEY;
|
|
else if (!strncmp("WARNING: The signing key is not trusted", buf, 39))
|
|
res = RPMSIG_NOTTRUSTED;
|
|
else if (!strncmp("Good signature", buf, 14))
|
|
res = RPMSIG_OK;
|
|
}
|
|
(void) fclose(file);
|
|
*t = '\0';
|
|
}
|
|
|
|
(void) waitpid(pid, &status, 0);
|
|
if (sigfile) (void) unlink(sigfile);
|
|
sigfile = _free(sigfile);
|
|
if (!res && (!WIFEXITED(status) || WEXITSTATUS(status))) {
|
|
res = RPMSIG_BAD;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static rpmVerifySignatureReturn
|
|
do_verifyGPGSignature(const char *gpghome, const char *sigfile, const char *datafile, /*@out@*/ char *result)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies *result, rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
int pid, outpipe[2];
|
|
|
|
if ( !sigfile || !datafile )
|
|
return RPMSIG_BAD;
|
|
|
|
/* Now run GPG */
|
|
outpipe[0] = outpipe[1] = 0;
|
|
pipe(outpipe);
|
|
|
|
pid = fork();
|
|
|
|
if ( pid < 0 )
|
|
{
|
|
rpmError( RPMERR_FORK, _("Couldn't fork %s: %s"), "gpg", strerror(errno) );
|
|
return RPMSIG_BAD;
|
|
} else
|
|
{
|
|
if ( !pid )
|
|
{
|
|
/* child */
|
|
const char * cmd;
|
|
char *const *av;
|
|
int rc;
|
|
|
|
close( outpipe[0] );
|
|
/* gpg version 0.9 sends its output to stderr. */
|
|
dup2(outpipe[1], STDERR_FILENO);
|
|
|
|
addMacro(NULL, "__plaintext_filename", NULL, datafile, -1);
|
|
addMacro(NULL, "__signature_filename", NULL, sigfile, -1);
|
|
|
|
if ( rpm_close_all() ) {
|
|
perror( "rpm_close_all" );
|
|
_exit( -1 );
|
|
}
|
|
|
|
dosetenv( "LANG", "C", 1 );
|
|
dosetenv( "LANGUAGE", "C", 1 );
|
|
dosetenv( "LC_ALL", "C", 1 );
|
|
|
|
if ( gpghome && *gpghome )
|
|
dosetenv( "GNUPGHOME", gpghome, 1 );
|
|
|
|
cmd = rpmExpand("%{?__gpg_verify_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execv(av[0], av+1);
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "gpg",
|
|
strerror(errno));
|
|
_exit( RPMERR_EXEC );
|
|
} else
|
|
{
|
|
/* parent */
|
|
int res = RPMSIG_OK, status;
|
|
FILE *file = fdopen( outpipe[0], "r" );
|
|
unsigned char buf[BUFSIZ];
|
|
const char nokey[] = "gpg: Can't check signature: public key not found";
|
|
|
|
close(outpipe[1]);
|
|
result[0] = '\0';
|
|
|
|
if ( file )
|
|
{
|
|
while ( fgets( buf, sizeof(buf), file ) )
|
|
{
|
|
strcat(result, buf);
|
|
if ( !xstrncasecmp( nokey, buf, sizeof(nokey)-1 ) )
|
|
res = RPMSIG_NOKEY;
|
|
}
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
while ( waitpid( pid, &status, 0 ) < 0 )
|
|
{
|
|
if ( EINTR != errno )
|
|
{
|
|
rpmError( RPMERR_FORK, _("waitpid failure: %s"), strerror(errno) );
|
|
return RPMSIG_BAD;
|
|
}
|
|
}
|
|
|
|
if ( !res && (!WIFEXITED(status) || WEXITSTATUS(status)) )
|
|
res = RPMSIG_BAD;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
static rpmVerifySignatureReturn
|
|
verifyGPGSignature(const char * datafile, const void * sig, int count,
|
|
/*@out@*/ char * result)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies *result, rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
const char *sigfile = 0;
|
|
|
|
/* Write out the signature */
|
|
{ FD_t sfd;
|
|
if (!makeTempFile(NULL, &sigfile, &sfd)) {
|
|
(void) Fwrite(sig, sizeof(char), count, sfd);
|
|
(void) Fclose(sfd);
|
|
sfd = NULL;
|
|
}
|
|
}
|
|
|
|
if (!sigfile)
|
|
{
|
|
rpmError(RPMERR_SCRIPT, _("Unable to open temp file."));
|
|
return RPMSIG_BAD;
|
|
}
|
|
|
|
/* Now run GPG */
|
|
{
|
|
const char *gpg_path = rpmExpand( "%{?_internal_gpg_path}", NULL );
|
|
const char *gpg_home = ( gpg_path && *gpg_path ) ? gpg_path : 0;
|
|
rpmVerifySignatureReturn res = gpg_home ?
|
|
do_verifyGPGSignature( gpg_home, sigfile, datafile, result ) : RPMSIG_NOKEY;
|
|
gpg_path = _free( gpg_path );
|
|
|
|
if ( RPMSIG_NOKEY == res )
|
|
{
|
|
gpg_path = rpmExpand( "%{?_gpg_path}", NULL );
|
|
gpg_home = ( gpg_path && *gpg_path ) ? gpg_path : 0;
|
|
res = do_verifyGPGSignature( gpg_home, sigfile, datafile, result );
|
|
gpg_path = _free( gpg_path );
|
|
}
|
|
|
|
unlink(sigfile);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
static int checkPassPhrase(const char * passPhrase, const int sigTag)
|
|
/*@globals rpmGlobalMacroContext, fileSystem @*/
|
|
/*@modifies rpmGlobalMacroContext, fileSystem @*/
|
|
{
|
|
int passPhrasePipe[2];
|
|
int pid, status;
|
|
int fd;
|
|
const char * cmd;
|
|
char *const *av;
|
|
int rc;
|
|
|
|
passPhrasePipe[0] = passPhrasePipe[1] = 0;
|
|
(void) pipe(passPhrasePipe);
|
|
if (!(pid = fork())) {
|
|
(void) close(STDIN_FILENO);
|
|
(void) close(STDOUT_FILENO);
|
|
(void) close(passPhrasePipe[1]);
|
|
if (! rpmIsVerbose()) {
|
|
(void) close(STDERR_FILENO);
|
|
}
|
|
if ((fd = open("/dev/null", O_RDONLY)) != STDIN_FILENO) {
|
|
(void) dup2(fd, STDIN_FILENO);
|
|
(void) close(fd);
|
|
}
|
|
if ((fd = open("/dev/null", O_WRONLY)) != STDOUT_FILENO) {
|
|
(void) dup2(fd, STDOUT_FILENO);
|
|
(void) close(fd);
|
|
}
|
|
(void) dup2(passPhrasePipe[0], 3);
|
|
|
|
switch (sigTag) {
|
|
case RPMSIGTAG_GPG:
|
|
{ const char *gpg_path = rpmExpand("%{?_gpg_path}", NULL);
|
|
|
|
if (gpg_path && *gpg_path != '\0')
|
|
(void) dosetenv("GNUPGHOME", gpg_path, 1);
|
|
|
|
cmd = rpmExpand("%{?__gpg_check_password_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "gpg",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
} /*@notreached@*/ break;
|
|
case RPMSIGTAG_PGP5: /* XXX legacy */
|
|
case RPMSIGTAG_PGP:
|
|
{ const char *pgp_path = rpmExpand("%{?_pgp_path}", NULL);
|
|
const char *path;
|
|
pgpVersion pgpVer;
|
|
|
|
(void) dosetenv("PGPPASSFD", "3", 1);
|
|
if (pgp_path && *pgp_path != '\0')
|
|
(void) dosetenv("PGPPATH", pgp_path, 1);
|
|
|
|
if ((path = rpmDetectPGPVersion(&pgpVer)) != NULL) {
|
|
switch(pgpVer) {
|
|
case PGP_2:
|
|
cmd = rpmExpand("%{?__pgp_check_password_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
break;
|
|
case PGP_5: /* XXX legacy */
|
|
cmd = rpmExpand("%{?__pgp5_check_password_cmd}", NULL);
|
|
rc = poptParseArgvString(cmd, NULL, (const char ***)&av);
|
|
if (!rc)
|
|
rc = execve(av[0], av+1, environ);
|
|
break;
|
|
case PGP_UNKNOWN:
|
|
case PGP_NOTDETECTED:
|
|
break;
|
|
}
|
|
}
|
|
rpmError(RPMERR_EXEC, _("Could not exec %s: %s\n"), "pgp",
|
|
strerror(errno));
|
|
_exit(RPMERR_EXEC);
|
|
} /*@notreached@*/ break;
|
|
default: /* This case should have been screened out long ago. */
|
|
rpmError(RPMERR_SIGGEN, _("Invalid %%_signature spec in macro file\n"));
|
|
_exit(RPMERR_SIGGEN);
|
|
/*@notreached@*/ break;
|
|
}
|
|
}
|
|
|
|
(void) close(passPhrasePipe[0]);
|
|
(void) write(passPhrasePipe[1], passPhrase, strlen(passPhrase));
|
|
(void) write(passPhrasePipe[1], "\n", 1);
|
|
(void) close(passPhrasePipe[1]);
|
|
|
|
(void)waitpid(pid, &status, 0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
|
|
return 1;
|
|
}
|
|
|
|
/* passPhrase is good */
|
|
return 0;
|
|
}
|
|
|
|
char * rpmGetPassPhrase(const char * prompt, const int sigTag)
|
|
{
|
|
char *pass;
|
|
int aok;
|
|
|
|
switch (sigTag) {
|
|
case RPMSIGTAG_GPG:
|
|
{ const char *name = rpmExpand("%{?_gpg_name}", NULL);
|
|
aok = (name && *name != '\0');
|
|
name = _free(name);
|
|
}
|
|
if (!aok) {
|
|
rpmError(RPMERR_SIGGEN,
|
|
_("You must set \"%%_gpg_name\" in your macro file\n"));
|
|
return NULL;
|
|
}
|
|
break;
|
|
case RPMSIGTAG_PGP5: /* XXX legacy */
|
|
case RPMSIGTAG_PGP:
|
|
{ const char *name = rpmExpand("%{?_pgp_name}", NULL);
|
|
aok = (name && *name != '\0');
|
|
name = _free(name);
|
|
}
|
|
if (!aok) {
|
|
rpmError(RPMERR_SIGGEN,
|
|
_("You must set \"%%_pgp_name\" in your macro file\n"));
|
|
return NULL;
|
|
}
|
|
break;
|
|
default:
|
|
/* Currently the calling function (rpm.c:main) is checking this and
|
|
* doing a better job. This section should never be accessed.
|
|
*/
|
|
rpmError(RPMERR_SIGGEN, _("Invalid %%_signature spec in macro file\n"));
|
|
return NULL;
|
|
/*@notreached@*/ break;
|
|
}
|
|
|
|
pass = /*@-unrecog@*/ getpass( (prompt ? prompt : "") ) /*@=unrecog@*/ ;
|
|
|
|
if (checkPassPhrase(pass, sigTag))
|
|
return NULL;
|
|
|
|
return pass;
|
|
}
|
|
|
|
rpmVerifySignatureReturn
|
|
rpmVerifySignature(const char * file, int_32 sigTag, const void * sig,
|
|
int count, char * result)
|
|
{
|
|
rpmVerifySignatureReturn res;
|
|
|
|
switch (sigTag) {
|
|
case RPMSIGTAG_SIZE:
|
|
res = verifySizeSignature(file, *(int_32 *)sig, result);
|
|
break;
|
|
case RPMSIGTAG_MD5:
|
|
res = verifyMD5Signature(file, sig, result, mdbinfile);
|
|
break;
|
|
case RPMSIGTAG_PGP5: /* XXX legacy */
|
|
case RPMSIGTAG_PGP:
|
|
res = verifyPGPSignature(file, sig, count, result);
|
|
break;
|
|
case RPMSIGTAG_GPG:
|
|
res = verifyGPGSignature(file, sig, count, result);
|
|
break;
|
|
case RPMSIGTAG_LEMD5_1:
|
|
case RPMSIGTAG_LEMD5_2:
|
|
sprintf(result, _("Broken MD5 digest: UNSUPPORTED\n"));
|
|
res = RPMSIG_UNKNOWN;
|
|
break;
|
|
default:
|
|
sprintf(result, "Do not know how to verify sig type %d\n", sigTag);
|
|
res = RPMSIG_UNKNOWN;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|