* [gentoo-commits] proj/portage-utils:master commit in: tests/qmanifest/root/.gnupg/, tests/qmanifest/, /, man/, man/include/, ...
@ 2019-05-24 11:58 Fabian Groffen
0 siblings, 0 replies; only message in thread
From: Fabian Groffen @ 2019-05-24 11:58 UTC (permalink / raw
To: gentoo-commits
commit: f1d02fbf01683c42ddb0cdfbfe7815c5ff37e035
Author: Fabian Groffen <grobian <AT> gentoo <DOT> org>
AuthorDate: Fri May 24 11:58:26 2019 +0000
Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Fri May 24 11:58:26 2019 +0000
URL: https://gitweb.gentoo.org/proj/portage-utils.git/commit/?id=f1d02fbf
qmanifest: allow GPG-signing top-level Manifest
Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org>
man/include/qmanifest-01-generation.include | 17 ++
man/include/qmanifest.optdesc.yaml | 8 +
man/qmanifest.1 | 30 ++-
qmanifest.c | 214 ++++++++++++++++-----
tests/qmanifest/dotest | 16 +-
tests/qmanifest/manifest04.good | 3 +-
tests/qmanifest/manifest07.good | 11 +-
.../1F0A2C7F1E80A6EEEA3B9C30068FB3349702B3A7.key | Bin 0 -> 1171 bytes
.../E37F9F3C8E4A940C625EC65B7070255F4AAA55F9.key | Bin 0 -> 1155 bytes
tests/qmanifest/root/.gnupg/pubring.kbx | Bin 0 -> 1435 bytes
tests/qmanifest/root/.gnupg/random_seed | Bin 0 -> 600 bytes
tests/qmanifest/root/.gnupg/trustdb.gpg | Bin 0 -> 1280 bytes
12 files changed, 233 insertions(+), 66 deletions(-)
diff --git a/man/include/qmanifest-01-generation.include b/man/include/qmanifest-01-generation.include
new file mode 100644
index 0000000..5a24a02
--- /dev/null
+++ b/man/include/qmanifest-01-generation.include
@@ -0,0 +1,17 @@
+.SH "GENERATING A SIGNED TREE"
+.PP
+By default, \fBqmanifest\fR will not try to sign the top-level Manifest
+when it generating thick Manifests. A tree as such isn't completely
+valid (as it misses the final signature), but still correct. To sign
+the top-level Manifest, the \fB-s\fR flag needs to be used to provide
+the GPG keyid to sign with. The passphrase is requested by \fBgpg\fR(1)
+itself, unless the \fB-p\fR flag is given, in which case \fBqmanifest\fR
+attempts to read the passphrase from \fIstdin\fR and then pass that
+passphrase onto \fBgpg\fR. This is useful for scenarios in which the
+signing of a tree is scripted.
+.PP
+To generate a tree signed by GPG keyid \fI0x123567ABC\fR using
+passphrase \fImypasswd\fR, one could use:
+.nf\fI
+ $ echo mypasswd | qmanifest -g -s 0x123567ABC -p /path/to/tree
+.fi
diff --git a/man/include/qmanifest.optdesc.yaml b/man/include/qmanifest.optdesc.yaml
new file mode 100644
index 0000000..8bf1ce7
--- /dev/null
+++ b/man/include/qmanifest.optdesc.yaml
@@ -0,0 +1,8 @@
+signas: |
+ Sign generated Manifest using GPG key. This key must exist in your
+ keyring and be valid for signing.
+passphrase: |
+ Ask for GPG key password (instead of relying on gpg-agent). While
+ this option is not very useful compared to gpg's ways of gathering a
+ password, it is mainly intended for automated setups where the
+ password is piped in using \fIstdin\fR.
diff --git a/man/qmanifest.1 b/man/qmanifest.1
index e223122..15027f6 100644
--- a/man/qmanifest.1
+++ b/man/qmanifest.1
@@ -38,7 +38,17 @@ with the desired maximum amount of threads in use by \fIqmanifest\fR.
.SH OPTIONS
.TP
\fB\-g\fR, \fB\-\-generate\fR
-Generate thick Manifests and sign.
+Generate thick Manifests.
+.TP
+\fB\-s\fR \fI<arg>\fR, \fB\-\-signas\fR \fI<arg>\fR
+Sign generated Manifest using GPG key. This key must exist in your
+keyring and be valid for signing.
+.TP
+\fB\-p\fR, \fB\-\-passphrase\fR
+Ask for GPG key password (instead of relying on gpg-agent). While
+this option is not very useful compared to gpg's ways of gathering a
+password, it is mainly intended for automated setups where the
+password is piped in using \fIstdin\fR.
.TP
\fB\-d\fR, \fB\-\-dir\fR
Treat arguments as directories.
@@ -63,7 +73,23 @@ Print this help and exit.
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version and exit.
-
+.SH "GENERATING A SIGNED TREE"
+.PP
+By default, \fBqmanifest\fR will not try to sign the top-level Manifest
+when it generating thick Manifests. A tree as such isn't completely
+valid (as it misses the final signature), but still correct. To sign
+the top-level Manifest, the \fB-s\fR flag needs to be used to provide
+the GPG keyid to sign with. The passphrase is requested by \fBgpg\fR(1)
+itself, unless the \fB-p\fR flag is given, in which case \fBqmanifest\fR
+attempts to read the passphrase from \fIstdin\fR and then pass that
+passphrase onto \fBgpg\fR. This is useful for scenarios in which the
+signing of a tree is scripted.
+.PP
+To generate a tree signed by GPG keyid \fI0x123567ABC\fR using
+passphrase \fImypasswd\fR, one could use:
+.nf\fI
+ $ echo mypasswd | qmanifest -g -s 0x123567ABC -p /path/to/tree
+.fi
.SH "REPORTING BUGS"
Please report bugs via http://bugs.gentoo.org/
.br
diff --git a/qmanifest.c b/qmanifest.c
index 88352fa..ed203a6 100644
--- a/qmanifest.c
+++ b/qmanifest.c
@@ -39,15 +39,19 @@
#include "eat_file.h"
#include "hash.h"
-#define QMANIFEST_FLAGS "gdo" COMMON_FLAGS
+#define QMANIFEST_FLAGS "gs:pdo" COMMON_FLAGS
static struct option const qmanifest_long_opts[] = {
{"generate", no_argument, NULL, 'g'},
+ {"signas", a_argument, NULL, 's'},
+ {"passphrase", no_argument, NULL, 'p'},
{"dir", no_argument, NULL, 'd'},
{"overlay", no_argument, NULL, 'o'},
COMMON_LONG_OPTS
};
static const char * const qmanifest_opts_help[] = {
- "Generate thick Manifests and sign",
+ "Generate thick Manifests",
+ "Sign generated Manifest using GPG key",
+ "Ask for GPG key password (instead of relying on gpg-agent)",
"Treat arguments as directories",
"Treat arguments as overlay names",
COMMON_OPTS_HELP
@@ -55,6 +59,8 @@ static const char * const qmanifest_opts_help[] = {
#define qmanifest_usage(ret) usage(ret, QMANIFEST_FLAGS, qmanifest_long_opts, qmanifest_opts_help, NULL, lookup_applet_idx("qmanifest"))
static int hashes = HASH_DEFAULT;
+static char *gpg_sign_key = NULL;
+static bool gpg_get_password = false;
/* linked list structure to hold verification complaints */
typedef struct verify_msg {
@@ -688,37 +694,124 @@ generate_dir(const char *dir, enum type_manifest mtype)
}
}
+static gpgme_error_t
+gpgme_pw_cb(void *opaque, const char *uid_hint, const char *pw_info,
+ int last_was_bad, int fd)
+{
+ char *pass = (char *)opaque;
+ size_t passlen = strlen(pass);
+ ssize_t ret;
+
+ (void)uid_hint;
+ (void)pw_info;
+ (void)last_was_bad;
+
+ do {
+ ret = write(fd, pass, passlen);
+ if (ret > 0) {
+ pass += ret;
+ passlen -= ret;
+ }
+ } while (passlen > 0 && ret > 0);
+
+ return passlen == 0 ? GPG_ERR_NO_ERROR : gpgme_error_from_errno(errno);
+}
+
static const char *
-process_dir_gen(const char *dir)
+process_dir_gen(void)
{
char path[_Q_PATH_MAX];
int newhashes;
- int curdirfd;
+ struct termios termio;
+ char *gpg_pass;
- snprintf(path, sizeof(path), "%s%s/metadata/layout.conf", portroot, dir);
- if ((newhashes = parse_layout_conf(path)) != 0) {
+ if ((newhashes = parse_layout_conf("metadata/layout.conf")) != 0) {
hashes = newhashes;
} else {
return "generation must be done on a full tree";
}
- if ((curdirfd = open(".", O_RDONLY)) < 0) {
- fprintf(stderr, "cannot open current directory?!? %s\n",
- strerror(errno));
- }
- snprintf(path, sizeof(path), "%s%s", portroot, dir);
- if (chdir(path) != 0) {
- fprintf(stderr, "cannot chdir() to %s: %s\n", dir, strerror(errno));
- return "not a directory";
- }
-
if (generate_dir(".\0", GLOBAL_MANIFEST) == NULL)
return "generation failed";
- /* return to where we were before we called this function */
- if (fchdir(curdirfd) != 0 && verbose > 1)
- warn("could not move back to original directory");
- close(curdirfd);
+ if (gpg_sign_key != NULL) {
+ gpgme_ctx_t gctx;
+ gpgme_error_t gerr;
+ gpgme_key_t gkey;
+ gpgme_data_t manifest;
+ gpgme_data_t out;
+ FILE *f;
+ size_t dlen;
+
+ gerr = gpgme_new(&gctx);
+ if (gerr != GPG_ERR_NO_ERROR)
+ return "GPG setup failed";
+
+ gerr = gpgme_get_key(gctx, gpg_sign_key, &gkey, 0);
+ if (gerr != GPG_ERR_NO_ERROR)
+ return "failed to get GPG key";
+ gerr = gpgme_signers_add(gctx, gkey);
+ if (gerr != GPG_ERR_NO_ERROR)
+ return "failed to add GPG key to sign list, is it a suitable key?";
+
+ gpg_pass = NULL;
+ if (gpg_get_password) {
+ if (isatty(fileno(stdin))) {
+ /* disable terminal echo; the printing of what you type */
+ tcgetattr(fileno(stdin), &termio);
+ termio.c_lflag &= ~ECHO;
+ tcsetattr(fileno(stdin), TCSANOW, &termio);
+
+ printf("Password for GPG-key %s: ", gpg_sign_key);
+ }
+
+ gpg_pass = fgets(path, sizeof(path), stdin);
+
+ if (isatty(fileno(stdin))) {
+ printf("\n");
+ /* restore echoing, for what it's worth */
+ termio.c_lflag |= ECHO;
+ tcsetattr(fileno(stdin), TCSANOW, &termio);
+ }
+
+ if (gpg_pass == NULL || *gpg_pass == '\0')
+ warn("no GPG password given, gpg might ask for it again");
+ /* continue for the case where gpg-agent holds the pass */
+ else {
+ gpgme_set_pinentry_mode(gctx, GPGME_PINENTRY_MODE_LOOPBACK);
+ gpgme_set_passphrase_cb(gctx, gpgme_pw_cb, gpg_pass);
+ }
+ }
+
+ if ((f = fopen(str_manifest, "r+")) == NULL)
+ return "could not open top-level Manifest file";
+
+ /* finally, sign the Manifest */
+ if (gpgme_data_new_from_stream(&manifest, f) != GPG_ERR_NO_ERROR)
+ return "failed to create GPG data from Manifest";
+
+ if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR)
+ return "failed to create GPG output buffer";
+
+ gerr = gpgme_op_sign(gctx, manifest, out, GPGME_SIG_MODE_CLEAR);
+ if (gerr != GPG_ERR_NO_ERROR) {
+ warn("%s: %s", gpgme_strsource(gerr), gpgme_strerror(gerr));
+ return "failed to GPG sign Manifest";
+ }
+
+ /* write back signed Manifest */
+ rewind(f);
+ gpgme_data_seek(out, 0, SEEK_SET);
+ do {
+ dlen = gpgme_data_read(out, path, sizeof(path));
+ fwrite(path, dlen, 1, f);
+ } while (dlen == sizeof(path));
+ fclose(f);
+
+ gpgme_data_release(out);
+ gpgme_data_release(manifest);
+ gpgme_release(gctx);
+ }
return NULL;
}
@@ -854,13 +947,19 @@ verify_gpg_sig(const char *path, verify_msg **msgs)
"used to verify the signature has been revoked");
break;
case GPG_ERR_BAD_SIGNATURE:
+ free(ret);
+ ret = NULL;
printf("the signature is invalid\n");
break;
case GPG_ERR_NO_PUBKEY:
+ free(ret);
+ ret = NULL;
printf("the signature could not be verified due to a "
"missing key\n");
break;
default:
+ free(ret);
+ ret = NULL;
printf("there was some other error which prevented the "
"signature verification\n");
break;
@@ -1414,7 +1513,7 @@ format_line(const char *pfx, const char *msg, int twidth)
}
static const char *
-process_dir_vrfy(const char *dir)
+process_dir_vrfy(void)
{
char buf[8192];
int newhashes;
@@ -1422,7 +1521,6 @@ process_dir_vrfy(const char *dir)
struct timeval startt;
struct timeval finisht;
double etime;
- int curdirfd;
char *timestamp;
verify_msg topmsg;
verify_msg *walk = &topmsg;
@@ -1436,35 +1534,25 @@ process_dir_vrfy(const char *dir)
gettimeofday(&startt, NULL);
- snprintf(buf, sizeof(buf), "%s%s/metadata/layout.conf", portroot, dir);
+ snprintf(buf, sizeof(buf), "metadata/layout.conf");
if ((newhashes = parse_layout_conf(buf)) != 0) {
hashes = newhashes;
} else {
return "verification must be done on a full tree";
}
- if ((curdirfd = open(".", O_RDONLY)) < 0) {
- fprintf(stderr, "cannot open current directory?!? %s\n",
- strerror(errno));
- }
- snprintf(buf, sizeof(buf), "%s%s", portroot, dir);
- if (chdir(buf) != 0) {
- fprintf(stderr, "cannot chdir() to %s: %s\n", dir, strerror(errno));
- return "not a directory";
- }
-
if ((gs = verify_gpg_sig(str_manifest, &walk)) == NULL) {
ret = "gpg signature invalid";
} else {
fprintf(stdout,
"%s%s%s signature made %s by\n"
- "%s\n"
+ " %s%s%s\n"
"primary key fingerprint %s\n"
"%4s subkey fingerprint %s\n",
gs->isgood ? GREEN : RED,
gs->isgood ? "good": "BAD",
NORM, gs->timestamp,
- gs->signer,
+ DKBLUE, gs->signer, NORM,
gs->pkfingerprint,
gs->algo, gs->fingerprint);
if (!gs->isgood)
@@ -1575,11 +1663,6 @@ process_dir_vrfy(const char *dir)
gettimeofday(&finisht, NULL);
- /* return to where we were before we called this function */
- if (fchdir(curdirfd) != 0 && verbose > 1)
- warn("could not move back to original directory");
- close(curdirfd);
-
etime = ((double)((finisht.tv_sec - startt.tv_sec) * 1000000 +
finisht.tv_usec) - (double)startt.tv_usec) / 1000000.0;
printf("checked %zd Manifests, %zd files, %zd failures in %.02fs\n",
@@ -1591,15 +1674,17 @@ int
qmanifest_main(int argc, char **argv)
{
char *prog;
- const char *(*runfunc)(const char *);
+ const char *(*runfunc)(void);
int ret;
const char *rsn;
bool isdir = false;
bool isoverlay = false;
char *overlay;
char path[_Q_PATH_MAX];
+ char path2[_Q_PATH_MAX];
size_t n;
int i;
+ int curdirfd;
if ((prog = strrchr(argv[0], '/')) == NULL) {
prog = argv[0];
@@ -1620,6 +1705,8 @@ qmanifest_main(int argc, char **argv)
switch (ret) {
COMMON_GETOPTS_CASES(qmanifest)
case 'g': runfunc = process_dir_gen; break;
+ case 's': gpg_sign_key = optarg; break;
+ case 'p': gpg_get_password = true; break;
case 'd': isdir = true; break;
case 'o': isoverlay = true; break;
}
@@ -1653,6 +1740,9 @@ qmanifest_main(int argc, char **argv)
}
}
+ if ((curdirfd = open(".", O_RDONLY)) < 0)
+ warn("cannot open current directory?!? %s\n", strerror(errno));
+
ret = EXIT_SUCCESS;
argc -= optind;
argv += optind;
@@ -1679,20 +1769,27 @@ qmanifest_main(int argc, char **argv)
if (isdir || (!isoverlay && overlay == NULL)) /* !isdir && !isoverlay */
overlay = argv[i];
- if (runfunc == process_dir_vrfy)
- printf("verifying %s%s%s...\n", BOLD, overlay, NORM);
-
if (*overlay != '/') {
if (portroot[1] == '\0') {
/* resolve the path */
+ (void)fchdir(curdirfd);
(void)realpath(overlay, path);
} else {
snprintf(path, sizeof(path), "./%s", overlay);
}
- overlay = path;
}
- rsn = runfunc(overlay);
+ snprintf(path2, sizeof(path2), "%s%s", portroot, path);
+ if (chdir(path2) != 0) {
+ warn("cannot change directory to %s: %s", overlay, strerror(errno));
+ ret |= 1;
+ continue;
+ }
+
+ if (runfunc == process_dir_vrfy)
+ printf("verifying %s%s%s...\n", BOLD, overlay, NORM);
+
+ rsn = runfunc();
if (rsn != NULL) {
printf("%s%s%s\n", RED, rsn, NORM);
ret |= 2;
@@ -1700,15 +1797,28 @@ qmanifest_main(int argc, char **argv)
}
if (i == 0) {
- if (runfunc == process_dir_vrfy)
- printf("verifying %s%s%s...\n", BOLD, main_overlay, NORM);
- rsn = runfunc(main_overlay);
- if (rsn != NULL) {
- printf("%s%s%s\n", RED, rsn, NORM);
- ret |= 2;
+ snprintf(path, sizeof(path), "%s%s", portroot, main_overlay);
+ if (chdir(path) != 0) {
+ warn("cannot change directory to %s: %s",
+ main_overlay, strerror(errno));
+ ret |= 1;
+ } else {
+ if (runfunc == process_dir_vrfy)
+ printf("verifying %s%s%s...\n", BOLD, main_overlay, NORM);
+
+ rsn = runfunc();
+ if (rsn != NULL) {
+ printf("%s%s%s\n", RED, rsn, NORM);
+ ret |= 2;
+ }
}
}
+ /* return to where we were before we called this function */
+ if (fchdir(curdirfd) != 0 && verbose > 1)
+ warn("could not move back to original directory");
+ close(curdirfd);
+
return ret;
}
diff --git a/tests/qmanifest/dotest b/tests/qmanifest/dotest
index 636a723..fb2aa22 100755
--- a/tests/qmanifest/dotest
+++ b/tests/qmanifest/dotest
@@ -36,7 +36,7 @@ test 02 2 "qmanifest not_a_tree"
test 03 2 "qmanifest notatree"
# dir test
-test 04 2 "qmanifest -d not_a_tree"
+test 04 1 "qmanifest -d not_a_tree"
# overlay test
test 05 1 "qmanifest -o notatree"
@@ -44,11 +44,19 @@ test 05 1 "qmanifest -o notatree"
# generate a valid tree
rm -Rf testtree
cp -r "${ROOT}/simpletree" testtree || echo try it anyway
+# make it a fully valid tree
+export HOME=${ROOT} # for gnupg home
+rm testtree/my-cat/mypackage/unrecorded-file
unset ROOT PORTAGE_CONFIGROOT
-test 06 0 "qmanifest -g testtree"
+SIGNAS=0x3D695C8C0F87966B62DC5AFCDCFABA8E07F52261
+KEYPASS=qmanifest
+test 06 0 "echo ${KEYPASS} | qmanifest -g -s ${SIGNAS} -p testtree"
-# validate the just generated tree (doesn't do GPG signing hence fails)
-test 07 0 "qmanifest testtree | sed '/Manifest timestamp/d'"
+# validate the just generated tree
+test 07 0 "qmanifest testtree | sed -e '/Manifest timestamp/d' -e 's/made .* UTC by/made by/'"
+
+# shut down agents and whatnot
+gpgconf --kill all
cleantmpdir
diff --git a/tests/qmanifest/manifest04.good b/tests/qmanifest/manifest04.good
index 17c6a1f..4831674 100644
--- a/tests/qmanifest/manifest04.good
+++ b/tests/qmanifest/manifest04.good
@@ -1,2 +1 @@
-verifying not_a_tree...
-verification must be done on a full tree
+manifest: cannot change directory to not_a_tree: No such file or directory
diff --git a/tests/qmanifest/manifest07.good b/tests/qmanifest/manifest07.good
index 67176c5..6347806 100644
--- a/tests/qmanifest/manifest07.good
+++ b/tests/qmanifest/manifest07.good
@@ -1,7 +1,6 @@
verifying testtree...
-Manifest:
-- failed to verify signature
-my-cat/mypackage/Manifest:
-- file not listed: unrecorded-file
-checked 5 Manifests, 9 files, 1 failures
-manifest verification failed
+good signature made by
+ Qmanifest Test Key
+primary key fingerprint 3D69 5C8C 0F87 966B 62DC 5AFC DCFA BA8E 07F5 2261
+ RSA subkey fingerprint 3D69 5C8C 0F87 966B 62DC 5AFC DCFA BA8E 07F5 2261
+checked 5 Manifests, 9 files, 0 failures
diff --git a/tests/qmanifest/root/.gnupg/private-keys-v1.d/1F0A2C7F1E80A6EEEA3B9C30068FB3349702B3A7.key b/tests/qmanifest/root/.gnupg/private-keys-v1.d/1F0A2C7F1E80A6EEEA3B9C30068FB3349702B3A7.key
new file mode 100644
index 0000000..b4ed767
Binary files /dev/null and b/tests/qmanifest/root/.gnupg/private-keys-v1.d/1F0A2C7F1E80A6EEEA3B9C30068FB3349702B3A7.key differ
diff --git a/tests/qmanifest/root/.gnupg/private-keys-v1.d/E37F9F3C8E4A940C625EC65B7070255F4AAA55F9.key b/tests/qmanifest/root/.gnupg/private-keys-v1.d/E37F9F3C8E4A940C625EC65B7070255F4AAA55F9.key
new file mode 100644
index 0000000..4b07401
Binary files /dev/null and b/tests/qmanifest/root/.gnupg/private-keys-v1.d/E37F9F3C8E4A940C625EC65B7070255F4AAA55F9.key differ
diff --git a/tests/qmanifest/root/.gnupg/pubring.kbx b/tests/qmanifest/root/.gnupg/pubring.kbx
new file mode 100644
index 0000000..848dc93
Binary files /dev/null and b/tests/qmanifest/root/.gnupg/pubring.kbx differ
diff --git a/tests/qmanifest/root/.gnupg/random_seed b/tests/qmanifest/root/.gnupg/random_seed
new file mode 100644
index 0000000..d32d054
Binary files /dev/null and b/tests/qmanifest/root/.gnupg/random_seed differ
diff --git a/tests/qmanifest/root/.gnupg/trustdb.gpg b/tests/qmanifest/root/.gnupg/trustdb.gpg
new file mode 100644
index 0000000..78308c6
Binary files /dev/null and b/tests/qmanifest/root/.gnupg/trustdb.gpg differ
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2019-05-24 11:58 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-05-24 11:58 [gentoo-commits] proj/portage-utils:master commit in: tests/qmanifest/root/.gnupg/, tests/qmanifest/, /, man/, man/include/, Fabian Groffen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox